Add SharedMemory#write_object and #read_object; add SharedMemoryIO helper.
This commit is contained in:
parent
cf6b821b17
commit
cc663a8d7f
|
@ -1,6 +1,7 @@
|
|||
require 'process_shared/rt'
|
||||
require 'process_shared/libc'
|
||||
require 'process_shared/with_self'
|
||||
require 'process_shared/shared_memory_io'
|
||||
|
||||
module ProcessShared
|
||||
# Memory block shared across processes.
|
||||
|
@ -44,7 +45,9 @@ module ProcessShared
|
|||
LibC::PROT_READ | LibC::PROT_WRITE,
|
||||
LibC::MAP_SHARED,
|
||||
@fd,
|
||||
0)
|
||||
0).
|
||||
slice(0, size) # slice to get FFI::Pointer that knows its size
|
||||
# (and thus does bounds checking)
|
||||
|
||||
@finalize = self.class.make_finalizer(@pointer.address, @size, @fd)
|
||||
ObjectSpace.define_finalizer(self, @finalize)
|
||||
|
@ -52,9 +55,47 @@ module ProcessShared
|
|||
super(@pointer)
|
||||
end
|
||||
|
||||
# Write the serialization of +obj+ (using Marshal.dump) to this
|
||||
# shared memory object at +offset+ (in bytes).
|
||||
#
|
||||
# Raises IndexError if there is insufficient space.
|
||||
def put_object(offset, obj)
|
||||
io = SharedMemoryIO.new(self)
|
||||
io.seek(offset)
|
||||
Marshal.dump(obj, io)
|
||||
end
|
||||
|
||||
# Read the serialized object at +offset+ (in bytes) using
|
||||
# Marshal.load.
|
||||
#
|
||||
# @return [Object]
|
||||
def get_object(offset)
|
||||
io = to_shm_io
|
||||
io.seek(offset)
|
||||
Marshal.load(io)
|
||||
end
|
||||
|
||||
# Equivalent to {#put_object(0, obj)}
|
||||
def write_object(obj)
|
||||
Marshal.dump(obj, to_shm_io)
|
||||
end
|
||||
|
||||
# Equivalent to {#read_object(0, obj)}
|
||||
#
|
||||
# @return [Object]
|
||||
def read_object
|
||||
Marshal.load(to_shm_io)
|
||||
end
|
||||
|
||||
def close
|
||||
ObjectSpace.undefine_finalizer(self)
|
||||
@finalize.call
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def to_shm_io
|
||||
SharedMemoryIO.new(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,309 @@
|
|||
module ProcessShared
|
||||
# Does some bounds checking for EOF, but assumes underlying memory
|
||||
# object (FFI::Pointer) will do bounds checking, in particular the
|
||||
# {#_putbytes} method relies on this.
|
||||
#
|
||||
# Note: an unbounded FFI::Pointer may be converted into a bounded
|
||||
# pointer using +ptr.slice(0, size)+.
|
||||
class SharedMemoryIO
|
||||
|
||||
attr_accessor :pos
|
||||
attr_reader :mem
|
||||
|
||||
def initialize(mem)
|
||||
@mem = mem
|
||||
@pos = 0
|
||||
@closed = false # TODO: actually pay attention to this
|
||||
end
|
||||
|
||||
def <<(*args)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def advise(*args)
|
||||
# no-op
|
||||
end
|
||||
|
||||
def autoclose=(*args)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def autoclose?
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def binmode
|
||||
# no-op; always in binmode
|
||||
end
|
||||
|
||||
def binmode?
|
||||
true
|
||||
end
|
||||
|
||||
def bytes
|
||||
if block_given?
|
||||
until eof?
|
||||
yield _getbyte
|
||||
end
|
||||
else
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
alias_method :each_byte, :bytes
|
||||
|
||||
def chars
|
||||
raise NotImplementedError
|
||||
end
|
||||
alias_method :each_char, :chars
|
||||
|
||||
def close
|
||||
@closed = true
|
||||
end
|
||||
|
||||
def close_on_exec=(bool)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def close_on_exec?
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def close_read
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def close_write
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def closed?
|
||||
@closed
|
||||
end
|
||||
|
||||
def codepoints
|
||||
raise NotImplementedError
|
||||
end
|
||||
alias_method :each_codepoint, :codepoints
|
||||
|
||||
def each
|
||||
raise NotImplementedError
|
||||
end
|
||||
alias_method :each_line, :each
|
||||
alias_method :lines, :each
|
||||
|
||||
def eof?
|
||||
pos == mem.size
|
||||
end
|
||||
alias_method :eof, :eof?
|
||||
|
||||
def external_encoding
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def fcntl
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def fdatasync
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def fileno
|
||||
raise NotImplementedError
|
||||
end
|
||||
alias_method :to_i, :fileno
|
||||
|
||||
def flush
|
||||
# no-op
|
||||
end
|
||||
|
||||
def fsync
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def getbyte
|
||||
return nil if eof?
|
||||
_getbyte
|
||||
end
|
||||
|
||||
def getc
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def gets
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def internal_encoding
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def ioctl
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def tty?
|
||||
false
|
||||
end
|
||||
alias_method :isatty, :tty?
|
||||
|
||||
def lineno
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def lineno=
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def lines
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def pid
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
alias_method :tell, :pos
|
||||
|
||||
def print(*args)
|
||||
raise NotImplementedError
|
||||
end
|
||||
def printf(*args)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def putc(arg)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def puts(*args)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# FIXME: this doesn't match IO#read exactly (corner cases about
|
||||
# EOF and whether length was nil or not), but it's enough for
|
||||
# {Marshal::load}.
|
||||
def read(length = nil, buffer = nil)
|
||||
length ||= (mem.size - pos)
|
||||
buffer ||= ''
|
||||
|
||||
actual_length = [(mem.size - pos), length].min
|
||||
actual_length.times do
|
||||
buffer << _getbyte
|
||||
end
|
||||
buffer
|
||||
end
|
||||
|
||||
def read_nonblock(*args)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def readbyte
|
||||
raise EOFError if eof?
|
||||
_getbyte
|
||||
end
|
||||
|
||||
def readchar
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def readline
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def readlines
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def readpartial
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def reopen
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def rewind
|
||||
pos = 0
|
||||
end
|
||||
|
||||
def seek(amount, whence = IO::SEEK_SET)
|
||||
case whence
|
||||
when IO::SEEK_CUR
|
||||
self.pos += amount
|
||||
when IO::SEEK_END
|
||||
self.pos = (mem.size + amount)
|
||||
when IO::SEEK_SET
|
||||
self.pos = amount
|
||||
else
|
||||
raise ArgumentError, "bad seek whence #{whence}"
|
||||
end
|
||||
end
|
||||
|
||||
def set_encoding
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def stat
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def sync
|
||||
true
|
||||
end
|
||||
|
||||
def sync=
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def sysread(*args)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def sysseek(*args)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def syswrite(*args)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def to_io
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def ungetbyte
|
||||
raise IOError if pos == 0
|
||||
pos -= 1
|
||||
end
|
||||
|
||||
def ungetc
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def write(str)
|
||||
s = str.to_s
|
||||
_putbytes(s)
|
||||
s.size
|
||||
end
|
||||
|
||||
def write_nonblock(str)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Like {#getbyte} but does not perform eof check.
|
||||
def _getbyte
|
||||
b = mem.get_uchar(pos)
|
||||
self.pos += 1
|
||||
b
|
||||
end
|
||||
|
||||
def _putbytes(str)
|
||||
mem.put_bytes(pos, str, 0, str.size)
|
||||
self.pos += str.size
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -32,5 +32,30 @@ module ProcessShared
|
|||
|
||||
mem.get_int(0).must_equal(1234567)
|
||||
end
|
||||
|
||||
describe 'Object dump/load' do
|
||||
it 'writes serialized objects' do
|
||||
mem = SharedMemory.new(1024)
|
||||
pid = fork do
|
||||
mem.write_object(['a', 'b'])
|
||||
Kernel.exit!
|
||||
end
|
||||
::Process.wait(pid)
|
||||
mem.read_object.must_equal ['a', 'b']
|
||||
end
|
||||
|
||||
it 'raises IndexError when insufficient space' do
|
||||
mem = SharedMemory.new(2)
|
||||
proc { mem.write_object(['a', 'b']) }.must_raise(IndexError)
|
||||
end
|
||||
|
||||
it 'writes with an offset' do
|
||||
mem = SharedMemory.new(1024)
|
||||
mem.put_object(2, 'string')
|
||||
proc { mem.read_object }.must_raise(TypeError)
|
||||
proc { mem.get_object(0) }.must_raise(TypeError)
|
||||
mem.get_object(2).must_equal 'string'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue