diff --git a/config/configuration.yml.example b/config/configuration.yml.example index ac127a4b5..c528d764e 100644 --- a/config/configuration.yml.example +++ b/config/configuration.yml.example @@ -178,6 +178,10 @@ default: # the `gs` binary. Used to generate attachment thumbnails of PDF files. #gs_command: + # Timeout when generating thumbnails using the `convert` or `gs` command. + # Timeout is set in seconds. + #thumbnails_generation_timeout: 10 + # Configuration of MiniMagick font. # # Redmine uses MiniMagick in order to export a gantt chart to a PNG image. diff --git a/lib/redmine/configuration.rb b/lib/redmine/configuration.rb index 00b76cbc1..4fdd29184 100644 --- a/lib/redmine/configuration.rb +++ b/lib/redmine/configuration.rb @@ -27,7 +27,8 @@ module Redmine 'avatar_server_url' => 'https://www.gravatar.com', 'email_delivery' => nil, 'max_concurrent_ajax_uploads' => 2, - 'common_mark_enable_hardbreaks' => true + 'common_mark_enable_hardbreaks' => true, + 'thumbnails_generation_timeout' => 10 } @config = nil diff --git a/lib/redmine/thumbnail.rb b/lib/redmine/thumbnail.rb index 4c886a5b3..66104533b 100644 --- a/lib/redmine/thumbnail.rb +++ b/lib/redmine/thumbnail.rb @@ -18,6 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'fileutils' +require 'timeout' module Redmine module Thumbnail @@ -51,8 +52,25 @@ module Redmine else cmd = "#{shell_quote CONVERT_BIN} #{shell_quote source} -auto-orient -thumbnail #{shell_quote size_option} #{shell_quote target}" end - unless system(cmd) - logger.error("Creating thumbnail failed (#{$?}):\nCommand: #{cmd}") + + pid = nil + begin + Timeout.timeout(Redmine::Configuration['thumbnails_generation_timeout'].to_i) do + pid = Process.spawn(cmd) + _, status = Process.wait2(pid) + unless status.success? + logger.error("Creating thumbnail failed (#{status.exitstatus}):\nCommand: #{cmd}") + return nil + end + end + rescue Timeout::Error + begin + Process.kill('TERM', pid) + Timeout.timeout(1) { Process.wait(pid) } + rescue Timeout::Error + Process.kill('KILL', pid) + end + logger.error("Creating thumbnail timed out:\nCommand: #{cmd}") return nil end end diff --git a/test/unit/attachment_test.rb b/test/unit/attachment_test.rb index 2e37672ab..b88350fba 100644 --- a/test/unit/attachment_test.rb +++ b/test/unit/attachment_test.rb @@ -626,6 +626,25 @@ class AttachmentTest < ActiveSupport::TestCase ensure set_tmp_attachments_directory end + + def test_thumbnail_should_timeout + dummy_pid = 37530 + Process.stubs(:spawn).returns(dummy_pid) + Process.stubs(:wait2).raises(Timeout::Error) + Process.stubs(:kill).returns(1) + Process.stubs(:wait).returns(dummy_pid) + Rails.logger.expects(:error).with(regexp_matches(/Creating thumbnail timed out/)) + + set_fixtures_attachments_directory + Attachment.clear_thumbnails + + attachment = Attachment.find(16) + thumbnail = attachment.thumbnail + + assert_nil thumbnail + ensure + set_tmp_attachments_directory + end else puts '(ImageMagick convert not available)' end