Handling keyboard interrupts
I have a minimal TCP server running in the SML/NJ
REPL and I am wondering how to gracefully close the listener socket on a keyboard interrupt. A stripped-down version of the server
fun sendHello sock =
let val res = "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello world!\r\n\r\n"
val slc = Word8VectorSlice.full (Byte.stringToBytes res)
in
Socket.sendVec (sock, slc);
Socket.close sock
end
fun acceptLoop serv =
let val (s, _) = Socket.accept serv
in print "Accepted a connection...\n";
sendHello s;
acceptLoop serv
end
fun serve port =
let val s = INetSock.TCP.socket()
in Socket.Ctl.setREUSEADDR (s, true);
Socket.bind(s, INetSock.any port);
Socket.listen(s, 5);
print "Entering accept loop...\n";
acceptLoop s
end
The problem is that if I start this server listening on a port, cancel it with a keyboard interrupt and then try to restart it on the same port, I get an error.
Standard ML of New Jersey v110.76 [built: Thu Feb 19 00:37:13 2015]
- use "test.sml" ;;
[opening test.sml]
[autoloading]
[library $SMLNJ-BASIS/basis.cm is stable]
[autoloading done]
val sendHello = fn : ('a,Socket.active Socket.stream) Socket.sock -> unit
val acceptLoop = fn : ('a,Socket.passive Socket.stream) Socket.sock -> 'b
val serve = fn : int -> 'a
val it = () : unit
- serve 8181 ;;
stdIn:2.1-2.11 Warning: type vars not generalized because of
value restriction are instantiated to dummy types (X1,X2,...)
Entering accept loop...
Accepted a connection...
C-c C-c
Interrupt
- serve 8181 ;;
stdIn:1.2-1.12 Warning: type vars not generalized because of
value restriction are instantiated to dummy types (X1,X2,...)
uncaught exception SysErr [SysErr: Address already in use [<UNKNOWN>]]
raised at: <bind.c>
-
So I would like to be able to close the listening socket when some error occurs. I can see Interrupt
in the REPL when I throw a keyboard interrupt, so I assumed that Interrupt
is the constructor of the exception that I have to catch. However, adding a suitable line handle
to acceptLoop
or serve
doesn't seem to do what I want.
fun acceptLoop serv =
let val (s, _) = Socket.accept serv
in print "Accepted a connection...\n";
sendHello s;
acceptLoop serv
end
handle Interrupt => Socket.close serv
fun serve port =
let val s = INetSock.TCP.socket()
in Socket.Ctl.setREUSEADDR (s, true);
Socket.bind(s, INetSock.any port);
Socket.listen(s, 5);
print "Entering accept loop...\n";
acceptLoop s
handle Interrupt => Socket.close s
end
(then in the REPL)
- use "test.sml" ;;
[opening test.sml]
val sendHello = fn : ('a,Socket.active Socket.stream) Socket.sock -> unit
val acceptLoop = fn : ('a,Socket.passive Socket.stream) Socket.sock -> 'b
val serve = fn : int -> 'a
val it = () : unit
- serve 8182 ;;
stdIn:3.1-3.11 Warning: type vars not generalized because of
value restriction are instantiated to dummy types (X1,X2,...)
Entering accept loop...
Accepted a connection...
C-c C-c
Interrupt
- serve 8182 ;;
stdIn:1.2-1.12 Warning: type vars not generalized because of
value restriction are instantiated to dummy types (X1,X2,...)
uncaught exception SysErr [SysErr: Address already in use [<UNKNOWN>]]
raised at: <bind.c>
-
Doing the same thing with matching variable ( handle x => (Socket.close s; raise x)
) or wildcard ( handle _ => Socket.close s
) exceptions has the same effect as above.
source to share
You are faced with a rather large limitation of the standard ML as such, which means that the standard language does not provide any provisions for parallel programming. And you need concurrency in this particular case.
Luckily you are using SML / NJ which has some extensions that support concurrency continuations .
In SML / NJ, you can install an interrupt handler and then resume whatever continuation of the program you want. This is what your function looks like serve
(I'm getting started myself when it comes to continuations in SML / NJ, so this is a hint rather than a "here's how" example):
fun serve port =
(*
* Capture the current continuation, which is basically the next REPL
* prompt after the server is done accepting requests.
*)
SMLofNJ.Cont.callcc (fn serverShutdownCont =>
let
val s = INetSock.TCP.socket()
(*
* The interrupt handler that is called when ^C is pressed.
* Shuts down the server and returns the continuation that should
* be resumed next, i.e. `serverShutdownCont`.
*)
fun interruptHandler (signal, n, cont) =
let in
print "Shutting down server... "
; Socket.close s
; print "done.\n"
; serverShutdownCont
end
in
(* Register the interrupt handler. *)
Signals.setHandler (Signals.sigINT, Signals.HANDLER interruptHandler);
Socket.Ctl.setREUSEADDR (s, true);
Socket.bind(s, INetSock.any port);
Socket.listen(s, 5);
print "Entering accept loop...\n";
acceptLoop s
end)
A very good resource to learn more about this is Unix System Programming with Standard ML , which has a small web server developed, so you will probably find it very useful.
Another thing you'll run into along the way is concurrency in the adoption loop. Right now, your program can only handle one HTTP request at a time. If you want to support more at a time, not necessarily in parallel, but at least concurrently (alternating), you will need to look into Concurrent ML (CML) , which is a parallel extension to standard ML, implemented as a library in addition to continuations provided by SML / NJ. CML comes with SML / NJ.
A very good CML tutorial written by library author John Reppy, Parallel Programming in ML . I recently worked on the first part of the book and it has been fully explained.
source to share