Skip to content

Instantly share code, notes, and snippets.

@agrare
Created February 6, 2018 17:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save agrare/d9484884bd297b1615814128129cfc5c to your computer and use it in GitHub Desktop.
Save agrare/d9484884bd297b1615814128129cfc5c to your computer and use it in GitHub Desktop.
DRb Fork Reproducer Script
# This script demonstrates the issue with mixing DRb calls and
# forking child processes.
#
# If the parent process calls a remote DRb method prior to forking
# a child process it appears there is still a connection in the
# DRbConn global connection pool. This seems to cause messages to
# be sent to the wrong client and typically leads to one of three
# symptoms:
#
# 1. A message is sent to the wrong client
# Ex: 'Failure! expected BBBB but got AAAA'
#
# 2. A message is spliced and what should be the message size is another string
# Ex: 'Failure! too large packet 1090927110'
#
# 3. A message is spliced and what should be the Marshal version is another string
# Ex: 'Failure! incompatible marshal file format (can't be read)
# format version 4.8 required; 14.4 given'
#
# For this case we added some client-side logging to dump the hex_bytes of the string:
# [TypeError]: incompatible marshal file format (can't be read)
# format version 4.8 required; 58.12 given
# 0x00000000 3a 0c 56 69 6d 48 61 73 68 7b 09 49 22 0b 65 6e :.VimHash{.I".en
# 0x00000010 74 69 74 79 06 3a 06 45 54 49 43 3a 0e 56 69 6d tity.:.ETIC:.Vim
# 0x00000020 53 74 72 69 6e 67 22 0d 76 6d 2d 31 38 37 35 37 String".vm-18757
#
# The first bytes here which should be the marshal version look like the middle of
# another message.
require 'drb/drb'
def echo_client(str)
echo_obj = DRbObject.new_with_uri('druby://localhost:9999')
1_000.times do
begin
ret = echo_obj.echo(str)
if ret != str
puts "PID #{Process.pid}: Failure! expected #{str} but got #{ret}"
exit 1
end
rescue TypeError, DRb::DRbConnError => err
puts "PID #{Process.pid}: Failure! #{err}"
exit 1
end
end
puts "PID #{Process.pid}: Succeded."
end
# Execute a DRb call in the parent process prior to forking the children
echo_obj = DRbObject.new_with_uri('druby://localhost:9999')
echo_obj.echo("CCCC") # Comment this out and the script passes
# Create 20 child processes to echo some string to the server and verify
# the same string was returned
child_pids = []
%w(AAAA BBBB).each do |str|
10.times do
child_pids << Kernel.fork { echo_client(str) }
end
end
child_pids.each { |pid| Process.wait(pid) }
require 'drb/drb'
class Echo
def echo(str)
str
end
end
DRb.start_service('druby://localhost:9999', Echo.new)
DRb.thread.join
$ ruby echo_server.rb &
[1] 28111
$ ruby echo_client.rb
PID 28134: Failure! too large packet 67654656
PID 28128: Failure! incompatible marshal file format (can't be read)
format version 4.8 required; 0.0 given
PID 28125: Failure! too large packet 67651874
PID 28144: Succeded.
PID 28147: Succeded.
PID 28192: Succeded.
PID 28176: Succeded.
PID 28167: Succeded.
PID 28186: Succeded.
PID 28152: Succeded.
PID 28150: Succeded.
PID 28131: Succeded.
PID 28170: Succeded.
PID 28137: Succeded.
PID 28173: Succeded.
PID 28164: Succeded.
PID 28179: Succeded.
PID 28182: Succeded.
PID 28139: Succeded.
PID 28158: Succeded.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment