diff --git a/.yardopts b/.yardopts new file mode 100644 index 0000000..88ee7de --- /dev/null +++ b/.yardopts @@ -0,0 +1 @@ +-m markdown -M redcarpet diff --git a/README.md b/README.md new file mode 100644 index 0000000..908034a --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +process_shared +============== + +Concurrency primitives that may be used in a cross-process way to +coordinate share memory between processes. + +FFI is used to access POSIX semaphore on Linux or Mach semaphores on +Mac. Atop these semaphores are implemented ProcessShared::Semaphore, +ProcessShared::Mutex. POSIX shared memory is used to implement +ProcessShared::SharedMemory. + +On Linux, POSIX semaphores support `sem_timedwait()` which can wait on +a semaphore but stop waiting after a timeout. + +Mac OS X's implementation of POSIX semaphores does not support +timeouts. But, the Mach layer in Mac OS X has its own semaphores that +do support timeouts. Thus, process_shared implements a moderate +subset of the Mach API, which is quite a bit different from POSIX. +Namely, semaphores created in one process are not available in child +processes created via `fork()`. Mach does provide the means to copy +capabilities between tasks (Mach equivalent to processes). +process_shared overrides Ruby's `fork` methods so that semaphores are +copied from parent to child to emulate the POSIX behavior. + +This is an incomplete work in progress. + +License +------- + +MIT + +Install +------- + +Install the gem with: + + gem install process_shared + +Usage +----- + +```ruby +require 'process_shared' + +mutex = ProcessShared::Mutex.new +mem = ProcessShared::SharedMemory.new(:int) # extends FFI::Pointer +mem.put_int(0, 0) + +pid1 = fork do + puts "in process 1 (#{Process.pid})" + 10.times do + sleep 0.01 + mutex.synchronize do + value = mem.get_int(0) + sleep 0.01 + puts "process 1 (#{Process.pid}) incrementing" + mem.put_int(0, value + 1) + end + end +end + +pid2 = fork do + puts "in process 2 (#{Process.pid})" + 10.times do + sleep 0.01 + mutex.synchronize do + value = mem.get_int(0) + sleep 0.01 + puts "process 2 (#{Process.pid}) decrementing" + mem.put_int(0, value - 1) + end + end +end + +Process.wait(pid1) +Process.wait(pid2) + +puts "value should be zero: #{mem.get_int(0)}" +``` + +Transfer Objects Across Processes +--------------------------------- + +```ruby +# allocate a sufficient memory block +mem = ProcessShared::SharedMemory.new(1024) + +# sub process can write (serialize) object to memory (with bounds checking) +pid = fork do + mem.write_object(['a', 'b']) +end + +Process.wait(pid) + +# parent process can read the object back (synchronizing access +# with a Mutex left as an excercie to reader) + +mem.read_object.must_equal ['a', 'b'] +``` + +Todo +---- + +* Test ConditionVariable +* Implement optional override of core Thread/Mutex classes +* Extend to win32? (See Python's processing library) +* Add finalizer to Mutex? (finalizer on Semaphore objects may be enough) or a method to + explicitly close and release resources? +* Test semantics of crashing processes who still hold locks, etc. +* Is SharedArray with Enumerable mixing sufficient Array-like interface? diff --git a/README.rdoc b/README.rdoc deleted file mode 100644 index 572a484..0000000 --- a/README.rdoc +++ /dev/null @@ -1,99 +0,0 @@ -== Description - -Concurrency primitives that may be used in a cross-process way to -coordinate share memory between processes. - -FFI is used to access POSIX semaphore on Linux or Mach semaphores on -Mac. Atop these semaphores are implemented ProcessShared::Semaphore, -ProcessShared::Mutex. POSIX shared memory is used to implement -ProcessShared::SharedMemory. - -On Linux, POSIX semaphores support sem_timedwait() which can wait on -a semaphore but stop waiting after a timeout. - -Mac OS X's implementation of POSIX semaphores does not support -timeouts. But, the Mach layer in Mac OS X has its own semaphores that -do support timeouts. Thus, process_shared implements a moderate -subset of the Mach API, which is quite a bit different from POSIX. -Namely, semaphores created in one process are not available in child -processes created via fork(). Mach does provide the means to copy -capabilities between tasks (Mach equivalent to processes). -process_shared overrides Ruby's fork methods so that semaphores are -copied from parent to child to emulate the POSIX behavior. - -This is an incomplete work in progress. - -== License - -MIT - -== Install -Install the gem with: - - gem install process_shared - -== Usage - - require 'process_shared' - - mutex = ProcessShared::Mutex.new - mem = ProcessShared::SharedMemory.new(:int) # extends FFI::Pointer - mem.put_int(0, 0) - - pid1 = fork do - puts "in process 1 (#{Process.pid})" - 10.times do - sleep 0.01 - mutex.synchronize do - value = mem.get_int(0) - sleep 0.01 - puts "process 1 (#{Process.pid}) incrementing" - mem.put_int(0, value + 1) - end - end - end - - pid2 = fork do - puts "in process 2 (#{Process.pid})" - 10.times do - sleep 0.01 - mutex.synchronize do - value = mem.get_int(0) - sleep 0.01 - puts "process 2 (#{Process.pid}) decrementing" - mem.put_int(0, value - 1) - end - end - end - - Process.wait(pid1) - Process.wait(pid2) - - puts "value should be zero: #{mem.get_int(0)}" - -== Transfer Objects Across Processes - - # allocate a sufficient memory block - mem = ProcessShared::SharedMemory.new(1024) - - # sub process can write (serialize) object to memory (with bounds checking) - pid = fork do - mem.write_object(['a', 'b']) - end - - Process.wait(pid) - - # parent process can read the object back (synchronizing access - # with a Mutex left as an excercie to reader) - - mem.read_object.must_equal ['a', 'b'] - -== Todo - -* Test ConditionVariable -* Implement optional override of core Thread/Mutex classes -* Extend to win32? (See Python's processing library) -* Add finalizer to Mutex? (finalizer on Semaphore objects may be enough) or a method to - explicitly close and release resources? -* Test semantics of crashing processes who still hold locks, etc. -* Is SharedArray with Enumerable mixing sufficient Array-like interface? diff --git a/process_shared.gemspec b/process_shared.gemspec index 0a3b520..7e6827e 100644 --- a/process_shared.gemspec +++ b/process_shared.gemspec @@ -3,7 +3,7 @@ Gem::Specification.new do |s| s.version = File.read(File.expand_path('../VERSION', __FILE__)) s.platform = Gem::Platform::RUBY s.has_rdoc = true - s.extra_rdoc_files = ["README.rdoc", "ChangeLog", "COPYING"] + s.extra_rdoc_files = ["README.md", "ChangeLog", "COPYING"] s.summary = 'process-shared synchronization primitives' s.description = 'FFI wrapper around portable semaphore library with mutex and condition vars built on top.' s.author = 'Patrick Mahoney' @@ -20,5 +20,6 @@ Gem::Specification.new do |s| s.add_development_dependency('minitest-matchers') s.add_development_dependency('rake') s.add_development_dependency('rake-compiler') + s.add_development_dependency('redcarpet') # for yardoc s.add_development_dependency('version') end