about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2013-04-05 21:21:19 +0000
committerEric Wong <normalperson@yhbt.net>2013-04-05 22:30:36 +0000
commit7f87cbc2e5289f328c3278a991519068d8747374 (patch)
treebcc48402f5231c4f03b6916affe9c0e0c3c95c20
parent803b2c842701ca74d504b61e8d35fd801e00c614 (diff)
downloadmahoro-7f87cbc2e5289f328c3278a991519068d8747374.tar.gz
add optional thread-safety module
This can make Mahoro easier-to-use in multi-threaded apps
where magic concurrency is not required.  For real concurrency,
it is recommended users create per-thread Mahoro objects.
-rw-r--r--doc.mk5
-rw-r--r--lib/mahoro/thread_safe.rb45
-rw-r--r--mahoro.c3
-rwxr-xr-xtest.rb9
4 files changed, 60 insertions, 2 deletions
diff --git a/doc.mk b/doc.mk
index 9d9af9b..2480585 100644
--- a/doc.mk
+++ b/doc.mk
@@ -1,7 +1,7 @@
 all::
 
-.ri/created.rid: mahoro.c
-        rdoc --ri -o $(@D) $<
+.ri/created.rid: mahoro.c lib/mahoro/thread_safe.rb
+        rdoc --ri -o $(@D) $^
 
 order =
 order += Mahoro
@@ -12,6 +12,7 @@ order += Mahoro\#flags=
 order += Mahoro\#valid?
 order += Mahoro\#compile
 order += Mahoro.compile
+order += Mahoro::ThreadSafe
 
 mahoro.txt: .ri/created.rid
         ( \
diff --git a/lib/mahoro/thread_safe.rb b/lib/mahoro/thread_safe.rb
new file mode 100644
index 0000000..4e77c35
--- /dev/null
+++ b/lib/mahoro/thread_safe.rb
@@ -0,0 +1,45 @@
+require 'thread'
+
+# Adds thread-safety to an existing Mahoro object.
+#
+# magic_t cookies of libmagic are not thread-safe by default,
+# thus Mahoro is not thread-safe by default, either.
+#
+# For applications using persistent thread pools, this module is NOT
+# recommended.  Instead, it is best to create thread-local instances
+# of (non-thread-safe) Mahoro for best performance.
+#
+# This is only intended for applications using Mahoro which meet the
+# following requirements:
+# 1) uses short-lived threads
+# 2) does not need high concurrency for Mahoro operations
+#
+# Usage example:
+#     require "mahoro/thread_safe"
+#
+#     # create a Mahoro object as usual
+#     mahoro_obj = Mahoro.new(...)
+#
+#     # enable thread-safety
+#     mahoro_obj.extend Mahoro::ThreadSafe
+#
+#     # mahoro_obj may now be used by multiple threads with automatic
+#     # mutual exclusion (the following example is safe, but not concurrent).
+#     Thread.new { mahoro_obj.file("/path/to/some_file") }
+#     Thread.new { mahoro_obj.file("/path/to/some_other_file") }
+#
+#     # Real concurrency must be achieved by using different Mahoro objects
+#     # in different threads.  As of Mahoro v0.4, Mahoro releases the GVL
+#     # on (Matz) Ruby 1.9 and later.
+module Mahoro::ThreadSafe
+
+  def self.extended(obj) # :nodoc:
+    obj.instance_variable_set(:@lock, Mutex.new)
+  end
+
+  eval(
+    %w(file buffer flags= valid? compile load).map do |meth|
+      "\ndef #{meth}(*args)\n  @lock.synchronize { super }\nend\n"
+    end.join("")
+  )
+end
diff --git a/mahoro.c b/mahoro.c
index a30946c..01d1cf8 100644
--- a/mahoro.c
+++ b/mahoro.c
@@ -384,6 +384,9 @@ void Init_mahoro(void)
          *        str = File.read('/path/to/file.c')
          *        mahoro_obj.buffer(str) -> 'ASCII C program text'
          *
+         * Mahoro is not thread-safe by default, see Mahoro::ThreadSafe for
+         * making this module thread-safe.
+         *
          * More information about libmagic:
          * https://en.wikipedia.org/wiki/Libmagic
          *
diff --git a/test.rb b/test.rb
index 75e46d6..5c2c23d 100755
--- a/test.rb
+++ b/test.rb
@@ -2,6 +2,7 @@
 
 require 'test/unit'
 require 'mahoro'
+require 'mahoro/thread_safe'
 require 'pathname'
 
 class MahoroTestCase < Test::Unit::TestCase
@@ -90,6 +91,14 @@ class MahoroTestCase < Test::Unit::TestCase
                         Mahoro.compile "magic.sample\0"
                 end
         end
+
+        def test_thread_safe
+                before = @m.method(:file)
+                @m.extend(Mahoro::ThreadSafe)
+                @m.flags = Mahoro::NONE
+                assert_c_text(@m.file('mahoro.c'))
+                assert_not_equal(before.object_id, @m.method(:file).object_id)
+        end
 end
 
 # arch-tag: test