docs/server.md in eventbox-0.1.0 vs docs/server.md in eventbox-1.0.0

- old
+ new

@@ -1,45 +1,55 @@ +## A TCP server implementation with tracking of startup and shutdown + Race-free server startup and shutdown can be a tricky task. The following example illustrates, how a TCP server can be started and interrupted properly. +For startup it makes use of {Eventbox::CompletionProc yield} and {Eventbox::CompletionProc#raise} to complete `MyServer.new` either successfully or with the forwarded exception raised by `TCPServer.new`. + +For the shutdown it makes use of {Eventbox::Action#raise} to send a `Stop` signal to the blocking `accept` method. +The `Stop` instance carries the {Eventbox::CompletionProc} which is used to signal that the shutdown has finished by returning from `MyServer#stop`. + ```ruby require "eventbox" require "socket" class MyServer < Eventbox yield_call def init(bind, port, result) @count = 0 - @server = start_serving(bind, port, result) + @server = start_serving(bind, port, result) # Start an action to handle incomming connections end action def start_serving(bind, port, init_done) serv = TCPServer.new(bind, port) rescue => err - init_done.raise err + init_done.raise err # complete MyServer.new with an exception else - init_done.yield + init_done.yield # complete MyServer.new without exception - loop do + loop do # accept all connection requests until Stop is received begin + # enable interruption by the Stop class for the duration of the `accept` call conn = Thread.handle_interrupt(Stop => :on_blocking) do - serv.accept + serv.accept # wait for the next connection request come in end rescue Stop => st serv.close - st.stopped.yield - break + st.stopped.yield # let MyServer#stop return + break # and exit the action else - MyConnection.new(conn, self) + MyConnection.new(conn, self) # Handle each client by its own instance end end end + # A simple example for a shared resource to be used by several threads sync_call def count - @count += 1 + @count += 1 # atomically increment the counter end yield_call def stop(result) + # Don't return from `stop` externally, but wait until the server is down @server.raise(Stop.new(result)) end class Stop < RuntimeError def initialize(stopped) @@ -47,30 +57,31 @@ end attr_reader :stopped end end +# Each call to `MyConnection.new` starts a new thread to do the communication. class MyConnection < Eventbox action def init(conn, server) conn.write "Hello #{server.count}" ensure - conn.close + conn.close # Don't wait for an answer but just close the client connection end end ``` The server can now be started like so. ```ruby -s = MyServer.new('localhost', 12345) +s = MyServer.new('localhost', 12345) # Open a TCP socket -10.times.map do +10.times.map do # run 10 client connections in parallel Thread.new do TCPSocket.new('localhost', 12345).read end -end.each { |th| p th.value } +end.each { |th| p th.value } # and print their responses -s.stop +s.stop # shutdown the server socket ``` It prints some output like this: ```ruby