better synchronization and threads use
authorGuillaume Cottenceau <gcottenc@gmail.com>
Tue, 16 Dec 2008 22:44:49 +0000 (23:44 +0100)
committerGuillaume Cottenceau <gcottenc@gmail.com>
Tue, 16 Dec 2008 22:44:49 +0000 (23:44 +0100)
bin/booh
lib/booh/Synchronizator.rb [deleted file]
lib/booh/booh-lib.rb

index 980c456..5a3a8db 100755 (executable)
--- a/bin/booh
+++ b/bin/booh
@@ -37,7 +37,6 @@ include REXML
 require 'booh/booh-lib'
 include Booh
 require 'booh/UndoHandler'
-require 'booh/Synchronizator'
 
 
 #- options
@@ -49,7 +48,6 @@ $options = [
 
 #- default values for some globals 
 $xmldir = nil
-$xmlaccesslock = Object.new
 $modified = false
 $current_cursor = nil
 $ignore_videos = false
@@ -518,33 +516,41 @@ end
 
 def color_swap(xmldir, attributes_prefix)
     $modified = true
-    if xmldir.attributes["#{attributes_prefix}color-swap"]
-        xmldir.delete_attribute("#{attributes_prefix}color-swap")
-    else
-        xmldir.add_attribute("#{attributes_prefix}color-swap", '1')
-    end
+    rexml_thread_protect {
+        if xmldir.attributes["#{attributes_prefix}color-swap"]
+            xmldir.delete_attribute("#{attributes_prefix}color-swap")
+        else
+            xmldir.add_attribute("#{attributes_prefix}color-swap", '1')
+        end
+    }
 end
 
 def enhance(xmldir, attributes_prefix)
     $modified = true
-    if xmldir.attributes["#{attributes_prefix}enhance"]
-        xmldir.delete_attribute("#{attributes_prefix}enhance")
-    else
-        xmldir.add_attribute("#{attributes_prefix}enhance", '1')
-    end
+    rexml_thread_protect {
+        if xmldir.attributes["#{attributes_prefix}enhance"]
+            xmldir.delete_attribute("#{attributes_prefix}enhance")
+        else
+            xmldir.add_attribute("#{attributes_prefix}enhance", '1')
+        end
+    }
 end
 
 def change_seektime(xmldir, attributes_prefix, value)
     $modified = true
-    xmldir.add_attribute("#{attributes_prefix}seektime", value)
+    rexml_thread_protect {
+        xmldir.add_attribute("#{attributes_prefix}seektime", value)
+    }
 end
 
 def ask_new_seektime(xmldir, attributes_prefix)
-    if xmldir
-        value = xmldir.attributes["#{attributes_prefix}seektime"]
-    else
-        value = ''
-    end
+    rexml_thread_protect {
+        if xmldir
+            value = xmldir.attributes["#{attributes_prefix}seektime"]
+        else
+            value = ''
+        end
+    }
 
     dialog = Gtk::Dialog.new(utf8(_("Change seek time")),
                              $main_window,
@@ -589,19 +595,23 @@ end
 
 def change_pano_amount(xmldir, attributes_prefix, value)
     $modified = true
-    if value.nil?
-        xmldir.delete_attribute("#{attributes_prefix}pano-amount")
-    else
-        xmldir.add_attribute("#{attributes_prefix}pano-amount", value.to_s)
-    end
+    rexml_thread_protect {
+        if value.nil?
+            xmldir.delete_attribute("#{attributes_prefix}pano-amount")
+        else
+            xmldir.add_attribute("#{attributes_prefix}pano-amount", value.to_s)
+        end
+    }
 end
 
 def ask_new_pano_amount(xmldir, attributes_prefix)
-    if xmldir
-        value = xmldir.attributes["#{attributes_prefix}pano-amount"]
-    else
-        value = nil
-    end
+    rexml_thread_protect {
+        if xmldir
+            value = xmldir.attributes["#{attributes_prefix}pano-amount"]
+        else
+            value = nil
+        end
+    }
 
     dialog = Gtk::Dialog.new(utf8(_("Specify panorama amount")),
                              $main_window,
@@ -846,16 +856,40 @@ def gen_real_thumbnail_core(type, origfile, destfile, xmldir, size, infotype)
     end
 end
 
+$max_gen_thumbnail_threads = nil
+$current_gen_thumbnail_threads = 0
+$gen_thumbnail_monitor = Monitor.new
+
 def gen_real_thumbnail(type, origfile, destfile, xmldir, size, img, infotype)
-    Thread.new {
+    if $max_gen_thumbnail_threads.nil?
+        $max_gen_thumbnail_threads = 1 + $config['mproc'].to_i || 1
+    end
+    genproc = Proc.new { 
         push_mousecursor_wait
-        gen_real_thumbnail_core(type, origfile, destfile, Synchronizator.new(xmldir, $xmlaccesslock), size, infotype)
+        gen_real_thumbnail_core(type, origfile, destfile, xmldir, size, infotype)
         gtk_thread_protect {
             img.set(destfile)
             $modified_pixbufs[destfile] = { :orig => img.pixbuf, :pixbuf => img.pixbuf, :angle_to_orig => 0 }
         }
         pop_mousecursor
     }
+    usethread = false
+    $gen_thumbnail_monitor.synchronize {
+        if $current_gen_thumbnail_threads < $max_gen_thumbnail_threads
+            $current_gen_thumbnail_threads += 1
+            usethread = true
+        end
+    }
+    if usethread
+        Thread.new {
+            genproc.call
+            $gen_thumbnail_monitor.synchronize {
+                $current_gen_thumbnail_threads -= 1
+            }
+        }
+    else
+        genproc.call
+    end
 end
 
 def popup_thumbnail_menu(event, optionals, fullpath, type, xmldir, attributes_prefix, possible_actions, closures)
@@ -1000,8 +1034,8 @@ def popup_thumbnail_menu(event, optionals, fullpath, type, xmldir, attributes_pr
         end
     }
     if !possible_actions[:can_multiple] || $selected_elements.length == 0
-        menu.append(enhance = Gtk::ImageMenuItem.new(utf8(xmldir.attributes["#{attributes_prefix}enhance"] ? _("Original contrast") :
-                                                                                                             _("Enhance constrast"))))
+        menu.append(enhance = Gtk::ImageMenuItem.new(utf8(rexml_thread_protect { xmldir.attributes["#{attributes_prefix}enhance"] } ? _("Original contrast") :
+                                                                                                                                      _("Enhance constrast"))))
     else
         menu.append(enhance = Gtk::ImageMenuItem.new(utf8(_("Toggle contrast enhancement"))))
     end
@@ -1170,13 +1204,17 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
         cleanup_all_thumbnails.call
         #- refresh is not undoable and doesn't change the album, however we must regenerate all thumbnails when generating the album
         $modified = true
-        $xmldir.delete_attribute('already-generated')
+        rexml_thread_protect {
+            $xmldir.delete_attribute('already-generated')
+        }
         my_gen_real_thumbnail.call
     }
  
     rotate_and_cleanup = proc { |angle|
         cleanup_all_thumbnails.call
-        rotate(angle, thumbnail_img, img, $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y])
+        rexml_thread_protect {
+            rotate(angle, thumbnail_img, img, $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y])
+        }
     }
 
     move = proc { |direction|
@@ -1207,7 +1245,9 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     color_swap_and_cleanup = proc {
         perform_color_swap_and_cleanup = proc {
             cleanup_all_thumbnails.call
-            color_swap($xmldir.elements["*[@filename='#{filename}']"], '')
+            rexml_thread_protect {
+                color_swap($xmldir.elements["*[@filename='#{filename}']"], '')
+            }
             my_gen_real_thumbnail.call
         }
 
@@ -1231,7 +1271,9 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     change_seektime_and_cleanup_real = proc { |values|
         perform_change_seektime_and_cleanup = proc { |val|
             cleanup_all_thumbnails.call
-            change_seektime($xmldir.elements["*[@filename='#{filename}']"], '', val)
+            rexml_thread_protect {
+                change_seektime($xmldir.elements["*[@filename='#{filename}']"], '', val)
+            }
             my_gen_real_thumbnail.call
         }
         perform_change_seektime_and_cleanup.call(values[:new])
@@ -1252,15 +1294,19 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     }
 
     change_seektime_and_cleanup = proc {
-        if values = ask_new_seektime($xmldir.elements["*[@filename='#{filename}']"], '')
-            change_seektime_and_cleanup_real.call(values)
-        end
+        rexml_thread_protect {
+            if values = ask_new_seektime($xmldir.elements["*[@filename='#{filename}']"], '')
+                change_seektime_and_cleanup_real.call(values)
+            end
+        }
     }
 
     change_pano_amount_and_cleanup_real = proc { |values|
         perform_change_pano_amount_and_cleanup = proc { |val|
             cleanup_all_thumbnails.call
-            change_pano_amount($xmldir.elements["*[@filename='#{filename}']"], '', val)
+            rexml_thread_protect {
+                change_pano_amount($xmldir.elements["*[@filename='#{filename}']"], '', val)
+            }
         }
         perform_change_pano_amount_and_cleanup.call(values[:new])
         
@@ -1280,17 +1326,21 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     }
 
     change_pano_amount_and_cleanup = proc {
-        if values = ask_new_pano_amount($xmldir.elements["*[@filename='#{filename}']"], '')
-            change_pano_amount_and_cleanup_real.call(values)
-        end
+        rexml_thread_protect {
+            if values = ask_new_pano_amount($xmldir.elements["*[@filename='#{filename}']"], '')
+                change_pano_amount_and_cleanup_real.call(values)
+            end
+        }
     }
 
     whitebalance_and_cleanup_real = proc { |values|
         perform_change_whitebalance_and_cleanup = proc { |val|
             cleanup_all_thumbnails.call
-            change_whitebalance($xmldir.elements["*[@filename='#{filename}']"], '', val)
-            recalc_whitebalance(val, fullpath, thumbnail_img, img,
-                                $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
+            rexml_thread_protect {
+                change_whitebalance($xmldir.elements["*[@filename='#{filename}']"], '', val)
+                recalc_whitebalance(val, fullpath, thumbnail_img, img,
+                                    $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
+            }
         }
         perform_change_whitebalance_and_cleanup.call(values[:new])
 
@@ -1310,18 +1360,22 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     }
 
     whitebalance_and_cleanup = proc {
-        if values = ask_whitebalance(fullpath, thumbnail_img, img,
-                                     $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
-            whitebalance_and_cleanup_real.call(values)
-        end
+        rexml_thread_protect {
+            if values = ask_whitebalance(fullpath, thumbnail_img, img,
+                                         $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
+                whitebalance_and_cleanup_real.call(values)
+            end
+        }
     }
 
     gammacorrect_and_cleanup_real = proc { |values|
         perform_change_gammacorrect_and_cleanup = Proc.new { |val|
             cleanup_all_thumbnails.call
-            change_gammacorrect($xmldir.elements["*[@filename='#{filename}']"], '', val)
-            recalc_gammacorrect(val, fullpath, thumbnail_img, img,
-                                $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
+            rexml_thread_protect {
+                change_gammacorrect($xmldir.elements["*[@filename='#{filename}']"], '', val)
+                recalc_gammacorrect(val, fullpath, thumbnail_img, img,
+                                    $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
+            }
         }
         perform_change_gammacorrect_and_cleanup.call(values[:new])
         
@@ -1341,16 +1395,20 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     }
     
     gammacorrect_and_cleanup = Proc.new {
-        if values = ask_gammacorrect(fullpath, thumbnail_img, img,
-                                     $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
-            gammacorrect_and_cleanup_real.call(values)
-        end
+        rexml_thread_protect {
+            if values = ask_gammacorrect(fullpath, thumbnail_img, img,
+                                         $xmldir.elements["*[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
+                gammacorrect_and_cleanup_real.call(values)
+            end
+        }
     }
     
     enhance_and_cleanup = proc {
         perform_enhance_and_cleanup = proc {
             cleanup_all_thumbnails.call
-            enhance($xmldir.elements["*[@filename='#{filename}']"], '')
+            rexml_thread_protect {
+                enhance($xmldir.elements["*[@filename='#{filename}']"], '')
+            }
             my_gen_real_thumbnail.call
         }
         
@@ -1562,7 +1620,7 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
             if !$ignore_next_release
                 x, y = autotable.get_current_pos(vbox)
                 next_ = autotable.get_next_widget(vbox)
-                popup_thumbnail_menu(event, ['delete'], fullpath, type, $xmldir.elements["*[@filename='#{filename}']"], '',
+                popup_thumbnail_menu(event, ['delete'], fullpath, type, rexml_thread_protect { $xmldir.elements["*[@filename='#{filename}']"] }, '',
                                      { :can_left => x > 0, :can_right => next_ && autotable.get_current_pos(next_)[0] > x,
                                        :can_up => y > 0, :can_down => y < autotable.get_max_y, :can_multiple => true, :can_panorama => true },
                                      { :rotate => rotate_and_cleanup, :move => move, :color_swap => color_swap_and_cleanup, :enhance => enhance_and_cleanup,
@@ -1868,7 +1926,7 @@ def ask_save_modifications(msg1, msg2, *options)
                 #- already-generated markers in original file
                 if $generated_outofline
                     begin
-                        $xmldoc = Synchronizator.new(REXML::Document.new(File.new($orig_filename)), $xmlaccesslock)
+                        $xmldoc = REXML::Document.new(File.new($orig_filename))
                         mark_document_as_dirty
                         ios = File.open($orig_filename, "w")
                         $xmldoc.write(ios, 0)
@@ -2215,10 +2273,12 @@ def sort_by_exif_date
     $modified = true
     save_changes
     current_order = []
-    $xmldir.elements.each { |element|
-        if element.name == 'image' || element.name == 'video'
-            current_order << element.attributes['filename']
-        end
+    rexml_thread_protect {
+        $xmldir.elements.each { |element|
+            if element.name == 'image' || element.name == 'video'
+                current_order << element.attributes['filename']
+            end
+        }
     }
 
     #- look for EXIF dates
@@ -2272,16 +2332,20 @@ def sort_by_exif_date
     end
 
     saves = {}
-    $xmldir.elements.each { |element|
-        if element.name == 'image' || element.name == 'video'
-            saves[element.attributes['filename']] = element.remove
-        end
+    rexml_thread_protect {
+        $xmldir.elements.each { |element|
+            if element.name == 'image' || element.name == 'video'
+                saves[element.attributes['filename']] = element.remove
+            end
+        }
     }
 
     neworder = smartsort(current_order, dates)
 
-    neworder.each { |f|
-        $xmldir.add_element(saves[f].name, saves[f].attributes)
+    rexml_thread_protect {
+        neworder.each { |f|
+            $xmldir.add_element(saves[f].name, saves[f].attributes)
+        }
     }
 
     #- let the auto-table reflect new ordering
@@ -2335,7 +2399,7 @@ def change_dir
     $subalbums = Gtk::Table.new(0, 0, true)
     current_y_sub_albums = 0
 
-    $xmldir = Synchronizator.new($xmldoc.elements["//dir[@path='#{$current_path}']"], $xmlaccesslock)
+    $xmldir = $xmldoc.elements["//dir[@path='#{$current_path}']"]
     $subalbums_edits = {}
     subalbums_counter = 0
     subalbums_edits_bypos = {}
@@ -2839,7 +2903,7 @@ def open_file(filename)
     end
 
     begin
-        $xmldoc = Synchronizator.new(REXML::Document.new(File.new(filename)), $xmlaccesslock)
+        $xmldoc = REXML::Document.new(File.new(filename))
     rescue Exception
         $xmldoc = nil
     end
@@ -4290,23 +4354,24 @@ def gtk_thread_protect(&proc)
     if Thread.current == Thread.main
         proc.call
     else
-        $protect_gtk_pending_calls.synchronize {
+        $gtk_pending_calls.synchronize {
             $gtk_pending_calls << proc
         }
     end
 end
 
 def gtk_thread_flush
-    #- try to lock. we cannot synchronize blindly because this might be called from
-    #- within the timeout flushing procs. if this is the case, not doing anything
-    #- should be ok since the timeout is already flushing them all.
-    if $protect_gtk_pending_calls.try_lock
-        for closure in $gtk_pending_calls
+    closure = nil
+    continue = true
+    begin
+        $gtk_pending_calls.synchronize {
+            closure = $gtk_pending_calls.shift
+            continue = $gtk_pending_calls.size > 0
+        }
+        if closure
             closure.call
         end
-        $gtk_pending_calls = []
-        $protect_gtk_pending_calls.unlock
-    end
+    end while continue
 end
 
 def ask_password_protect
@@ -4560,15 +4625,10 @@ def create_main_window
         false
     }
 
-    $protect_gtk_pending_calls = Mutex.new
     $gtk_pending_calls = []
+    $gtk_pending_calls.extend(MonitorMixin)
     Gtk.timeout_add(100) {
-        $protect_gtk_pending_calls.synchronize {
-            for closure in $gtk_pending_calls
-                closure.call
-            end
-            $gtk_pending_calls = []
-        }
+        gtk_thread_flush
         true
     }
 
diff --git a/lib/booh/Synchronizator.rb b/lib/booh/Synchronizator.rb
deleted file mode 100644 (file)
index 35bd115..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-#                         *  BOOH  *
-#
-# A.k.a `Best web-album Of the world, Or your money back, Humerus'.
-#
-# The acronyn sucks, however this is a tribute to Dragon Ball by
-# Akira Toriyama, where the last enemy beaten by heroes of Dragon
-# Ball is named "Boo". But there was already a free software project
-# called Boo, so this one will be it "Booh". Or whatever.
-#
-#
-# Copyright (c) 2004 Guillaume Cottenceau <gc3 at bluewin.ch>
-#
-# This software may be freely redistributed under the terms of the GNU
-# public license version 2.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-#
-#
-# Any method call to the wrapped object will be synchronized on a unique
-# monitor. Recursive calls are possible.
-#
-
-require 'monitor'
-
-class ObjectWrapper
-    def initialize(wrapped_object)
-        @__wrapped_object = wrapped_object
-    end
-    def method_missing(id, *args, &block)
-        __wrap(id, *args, &block)
-    end
-    def __wrap(id, *args, &block)
-        if block.nil?
-            return @__wrapped_object.__send__(id, *args)
-        else
-            return @__wrapped_object.__send__(id, *args) { |*args2| block.call(*args2) }
-        end
-    end
-end
-
-class Synchronizator < ObjectWrapper
-    def initialize(wrapped_object, monitor_object)
-        super(wrapped_object)
-        @__monitor_object = monitor_object
-        @__monitor_object.extend(MonitorMixin)
-    end
-    def __wrap(id, *args, &block)
-        @__monitor_object.synchronize {
-            super(id, *args, &block)
-        }
-    end
-end
index 13e63c4..1ea0231 100644 (file)
@@ -20,6 +20,7 @@
 require 'iconv'
 require 'timeout'
 require 'tempfile'
+require 'monitor'
 
 require 'booh/rexml/document'
 
@@ -321,9 +322,11 @@ module Booh
     end
 
     def gen_thumbnails_element(orig, xmldirorelem, allow_background, dests)
-        if xmldirorelem.name == 'dir'
-            xmldirorelem = xmldirorelem.elements["*[@filename='#{utf8(File.basename(orig))}']"]
-        end
+        rexml_thread_protect {
+            if xmldirorelem.name == 'dir'
+                xmldirorelem = xmldirorelem.elements["*[@filename='#{utf8(File.basename(orig))}']"]
+            end
+        }
         gen_thumbnails(orig, allow_background, dests, xmldirorelem, '')
     end
 
@@ -369,7 +372,7 @@ module Booh
 
         if entry2type(orig) == 'image'
             if felem
-                if whitebalance = felem.attributes["#{attributes_prefix}white-balance"]
+                if whitebalance = rexml_thread_protect { felem.attributes["#{attributes_prefix}white-balance"] }
                     neworig = "#{dest_dir}/#{File.basename(orig)}-whitebalance#{whitebalance}.jpg"
                     cmd = "booh-fix-whitebalance '#{orig}' '#{neworig}' #{whitebalance}"
                     sys(cmd)
@@ -377,7 +380,7 @@ module Booh
                         orig = neworig
                     end
                 end
-                if gammacorrect = felem.attributes["#{attributes_prefix}gamma-correction"]
+                if gammacorrect = rexml_thread_protect { felem.attributes["#{attributes_prefix}gamma-correction"] }
                     neworig = "#{dest_dir}/#{File.basename(orig)}-gammacorrect#{gammacorrect}.jpg"
                     cmd = "booh-gamma-correction '#{orig}' '#{neworig}' #{gammacorrect}"
                     sys(cmd)
@@ -385,12 +388,12 @@ module Booh
                         orig = neworig
                     end
                 end
-                rotate = felem.attributes["#{attributes_prefix}rotate"]
+                rotate = rexml_thread_protect { felem.attributes["#{attributes_prefix}rotate"] }
                 if !rotate
-                    felem.add_attribute("#{attributes_prefix}rotate", rotate = guess_rotate(orig).to_s)
+                    rexml_thread_protect { felem.add_attribute("#{attributes_prefix}rotate", rotate = guess_rotate(orig).to_s) }
                 end
                 convert_options += "-rotate #{rotate} "
-                if felem.attributes["#{attributes_prefix}enhance"]
+                if rexml_thread_protect { felem.attributes["#{attributes_prefix}enhance"] }
                     convert_options += ($config['convert-enhance'] || $convert_enhance) + " "
                 end
             end
@@ -421,27 +424,27 @@ module Booh
         elsif entry2type(orig) == 'video'
             if felem
                 #- seektime is an attribute that allows to specify where the frame to use for the thumbnail must be taken
-                seektime = felem.attributes["#{attributes_prefix}seektime"]
+                seektime = rexml_thread_protect { felem.attributes["#{attributes_prefix}seektime"] }
                 if ! seektime
-                    felem.add_attribute("#{attributes_prefix}seektime", seektime = "0")
+                    rexml_thread_protect { felem.add_attribute("#{attributes_prefix}seektime", seektime = "0") }
                 end
                 seektime = seektime.to_f
-                if rotate = felem.attributes["#{attributes_prefix}rotate"]
+                if rotate = rexml_thread_protect { felem.attributes["#{attributes_prefix}rotate"] }
                     convert_options += "-rotate #{rotate} "
                 end
-                if felem.attributes["#{attributes_prefix}enhance"]
+                if rexml_thread_protect { felem.attributes["#{attributes_prefix}enhance"] }
                     convert_options += ($config['convert-enhance'] || $convert_enhance) + " "
                 end
             end
             for dest in dests
                 if ! File.exists?(dest['filename'])
-                    tmpdir = gen_video_thumbnail(orig, felem && felem.attributes["#{attributes_prefix}color-swap"], seektime)
+                    tmpdir = gen_video_thumbnail(orig, felem && rexml_thread_protect { felem.attributes["#{attributes_prefix}color-swap"] }, seektime)
                     if tmpdir.nil?
                         return false
                     end
                     tmpfile = "#{tmpdir}/00000001.jpg"
                     alltmpfiles = [ tmpfile ]
-                    if felem && whitebalance = felem.attributes["#{attributes_prefix}white-balance"]
+                    if felem && whitebalance = rexml_thread_protect { felem.attributes["#{attributes_prefix}white-balance"] }
                         if whitebalance.to_f != 0
                             neworig = "#{tmpdir}/whitebalance#{whitebalance}.jpg"
                             cmd = "booh-fix-whitebalance '#{tmpfile}' '#{neworig}' #{whitebalance}"
@@ -452,7 +455,7 @@ module Booh
                             end
                         end
                     end
-                    if felem && gammacorrect = felem.attributes["#{attributes_prefix}gamma-correction"]
+                    if felem && gammacorrect = rexml_thread_protect { felem.attributes["#{attributes_prefix}gamma-correction"] }
                         if gammacorrect.to_f != 0
                             neworig = "#{tmpdir}/gammacorrect#{gammacorrect}.jpg"
                             cmd = "booh-gamma-correction '#{tmpfile}' '#{neworig}' #{gammacorrect}"
@@ -529,6 +532,14 @@ module Booh
         ios.close
     end
 
+    $xmlaccesslock = Monitor.new
+
+    def rexml_thread_protect(&proc)
+        $xmlaccesslock.synchronize {
+            proc.call
+        }
+    end
+
     def check_browser
         browser_binary = $config['browser'].split.first
         if browser_binary && !File.executable?(browser_binary)