This answer is possibly overly detailed, but I thought it would be good to go into a bit of depth for those who run across similar problems in the future.
It looks like this was your problem:
self[:value][:length].write_int(length)
when it should have been:
self[:value][:length].write_ulong(length)
On a 64 bit system, bytes 4..7 of the memory self[:value][:length] points to could have contained garbage (since malloc does not clear the memory it returns), and when the native code reads a size_t quantity at that address, it will be garbage, potentially indicating a buffer larger than 4 gigabytes.
e.g. if the string length is really 15 bytes, the lower 4 bits will be set, and the upper 60 should be all zero.
bit 0 1 2 3 4 32 63
+---+---+---+---+---+ ~ +---+ ~ +---+
| 1 | 1 | 1 | 1 | 0 | ~ | 0 | ~ | 0 |
+---+---+---+---+---+ ~ +---+ ~ +---+
if just one bit in that upper 32 bits is set, then you get a > 4 gigabyte value
bit 0 1 2 3 4 32 63
+---+---+---+---+---+ ~ +---+ ~ +---+
| 1 | 1 | 1 | 1 | 0 | ~ | 1 | ~ | 0 |
+---+---+---+---+---+ ~ +---+ ~ +---+
which would be a length of 4294967311 bytes.
One way to fix it, is to define a SizeT struct and use that for the length. e.g.
class SizeT < FFI::Struct
layout :value, :size_t
end
self[:value][:length] = SQLAnywhere::LibC.malloc(SizeT.size)
length = value.bytesize
SizeT.new(self[:value][:length])[:value] = length
or you could monkey patch FFI::Pointer:
class FFI::Pointer
if FFI.type_size(:size_t) == 4
def write_size_t(val)
write_int(val)
end
else
def write_size_t(val)
write_long_long(val)
end
end
end
Why was it only segfaulting on JRuby, not on MRI? Maybe MRI was a 32 bit executable (printing the value of FFI.type_size(:size_t) will tell you).