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