Project

General

Profile

Defect #44378 » 0002-Fix-the-gs-process-surviving-when-PDF-thumbnail-gene.patch

Go MAEDA, 2026-08-26 16:43

View differences:

lib/redmine/thumbnail.rb
58 58
        pid = nil
59 59
        begin
60 60
          Timeout.timeout(Redmine::Configuration['thumbnails_generation_timeout'].to_i) do
61
            pid = Process.spawn(cmd)
61
            # Run the command in its own process group so that the whole
62
            # process tree (eg. the gs process spawned by convert for PDF
63
            # files) can be killed on timeout. Process groups are not
64
            # available on Windows.
65
            spawn_options = Redmine::Platform.mswin? ? {} : {:pgroup => true}
66
            pid = Process.spawn(cmd, spawn_options)
62 67
            _, status = Process.wait2(pid)
63 68
            unless status.success?
64 69
              logger.error("Creating thumbnail failed (#{status.exitstatus}):\nCommand: #{cmd}")
......
67 72
          end
68 73
        rescue Timeout::Error
69 74
          if pid
70
            Process.kill('KILL', pid)
75
            begin
76
              # A negative pid sends the signal to the whole process group
77
              Process.kill('KILL', Redmine::Platform.mswin? ? pid : -pid)
78
            rescue Errno::ESRCH
79
              # The process is already gone
80
            end
71 81
            Process.detach(pid)
72 82
          end
73 83
          logger.error("Creating thumbnail timed out:\nCommand: #{cmd}")
test/unit/attachment_test.rb
787 787
      set_tmp_attachments_directory
788 788
    end
789
    def test_thumbnail_should_timeout
789
    def test_thumbnail_should_kill_and_detach_process_group_on_timeout
790 790
      dummy_pid = 37530
791
      Process.stubs(:spawn).returns(dummy_pid)
792
      Process.stubs(:wait2).raises(Timeout::Error)
793
      Process.stubs(:kill).returns(1)
794
      Process.stubs(:wait).returns(dummy_pid)
791

  
792
      Redmine::Platform.stubs(:mswin?).returns(false)
793
      Redmine::Thumbnail.stubs(:gs_available?).returns(true)
794
      Process.expects(:spawn).
795
        with(anything, {:pgroup => true}).
796
        returns(dummy_pid)
797
      Process.expects(:wait2).
798
        with(dummy_pid).
799
        raises(Timeout::Error)
800
      # A negative pid sends the signal to the whole process group so that
801
      # child processes of convert (eg. gs) are killed as well
802
      Process.expects(:kill).
803
        with('KILL', -dummy_pid).
804
        returns(1)
805
      Process.expects(:detach).
806
        with(dummy_pid)
795 807
      Rails.logger.expects(:error).with(regexp_matches(/Creating thumbnail timed out/))
796 808
      set_fixtures_attachments_directory
797 809
      Attachment.clear_thumbnails
798
      attachment = Attachment.find(16)
810
      # Use a PDF attachment because convert spawns a Ghostscript (gs)
811
      # child process for PDFs, which must not survive the timeout
812
      attachment = Attachment.find(23)
799 813
      thumbnail = attachment.thumbnail
800 814
      assert_nil thumbnail
801 815
    ensure
802 816
      set_tmp_attachments_directory
803 817
    end
818

  
804 819
  else
805 820
    puts '(ImageMagick convert not available)'
806 821
  end
(2-2/2)