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.

+3


source to share


1 answer


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.

+1


source







All Articles