package sturgeon

  1. Overview
  2. Docs

Source file sturgeon_recipes_server.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
open Sturgeon

type server = {
  client: unit -> (Session.t option * (Session.t -> unit) option);

  mutable socket: Unix.file_descr option;
  mutable clients: (Unix.file_descr) list;
  connections:
    (Unix.file_descr,
     (unit -> Sexp.basic option) * Session.output * Session.status)
    Hashtbl.t;
}

let server ~client name =
  let dir = Filename.concat (Filename.get_temp_dir_name ())
      (Printf.sprintf "sturgeon.%d" (Unix.getuid ())) in
  if not (Sys.file_exists dir) then
    Unix.mkdir dir 0o770;
  let name = Filename.concat dir
      (Printf.sprintf "%s.%d.sturgeon" name (Unix.getpid ())) in
  let socket = Unix.socket Unix.PF_UNIX Unix.SOCK_STREAM 0 in
  let addr = Unix.ADDR_UNIX name in
  Unix.bind socket addr;
  at_exit (fun () -> Unix.unlink name);
  Unix.listen socket 3;
  { socket = Some socket; clients = []; connections = Hashtbl.create 7;
    client }

let accept server =
  match server.socket with
  | None -> ()
  | Some socket ->
    let client, _ = Unix.accept socket in
    let oc = Unix.out_channel_of_descr client in
    let olock = Mutex.create () in
    let send sexp =
      Mutex.lock olock;
      try
        Sexp.tell_sexp (output_string oc) sexp;
        output_char oc '\n';
        flush oc;
        Mutex.unlock olock;
      with exn ->
        Mutex.unlock olock;
        raise exn
    in
    let greetings, cogreetings = server.client () in
    let received, status = Session.connect ?greetings ?cogreetings send in
    let stdin = Sexp.of_file_descr ~on_read:ignore client in
    server.clients <- client :: server.clients;
    Hashtbl.replace server.connections
      client (stdin, received, status)

let filter_fd server fd =
  if Hashtbl.mem server.connections fd then true
  else begin
    Unix.close fd;
    false
  end

let rec main_loop server =
  match server.socket with
  | None -> ()
  | Some socket ->
    server.clients <- List.filter (filter_fd server) server.clients;
    match Unix.select (socket :: server.clients) [] [] 1.0 with
    | exception Unix.Unix_error (Unix.EINTR, _, _) -> main_loop server
    | (r, _, _) ->
      let rec pump fd (stdin, received, status) =
        match stdin () with
        | None -> Hashtbl.remove server.connections fd
        | exception (Sys_error _) ->
          Hashtbl.remove server.connections fd
        | Some sexp ->
          begin try received sexp;
            with _ -> ()
          end;
          match Unix.select [fd] [] [] 0.0 <> ([],[],[]) with
          | true -> pump fd (stdin, received, status)
          | false -> ()
          | exception Unix.Unix_error (Unix.EINTR, _, _) -> ()
      in
      let process fd =
        if fd = socket then
          try accept server
          with _ -> ()
        else
          pump fd (Hashtbl.find server.connections fd)
      in
      List.iter process r;
      main_loop server

let main_loop ?(keep_sigpipe=false) server =
  if not keep_sigpipe then
    (try ignore (Sys.signal Sys.sigpipe Sys.Signal_ignore)
     with _ -> ());
  main_loop server

let stop_server server =
  match server.socket with
  | None -> ()
  | Some socket ->
    server.socket <- None;
    server.clients <- [];
    let clients = server.clients in
    Hashtbl.clear server.connections;
    List.iter Unix.close clients;
    Unix.close socket

let text_server name f =
  server ~client:(fun () ->
      let greetings, shell = Stui.buffer_greetings () in
      Some greetings, Some (fun args -> f ~args shell)
    )
    name
OCaml

Innovation. Community. Security.