Complete transition to epoll, all unit tests passing

This commit is contained in:
Neale Pickett 2008-03-18 15:27:03 -06:00
parent 9f058248ba
commit 667062bf93
9 changed files with 121 additions and 157 deletions

View File

@ -1,6 +1,4 @@
OCAMLPACKS[] =
equeue
pcre
str
OCAML_CLIBS = ocamlepoll
OCAMLCFLAGS += -g
@ -26,7 +24,7 @@ section
dispatch_tests.cmo:
dispatch_tests$(EXT_OBJ):
OCamlProgram(tests, tests dispatch chat irc command iobuf client channel)
OCamlProgram(tests, tests dispatch irc command iobuf client channel)
.PHONY: test
test: tests

View File

@ -40,7 +40,7 @@ let reply cli num ?(args=[]) text =
([!(cli.nick)] @ args)
(Some text))
let handle_close cli () =
let handle_close cli message =
Hashtbl.remove by_nick !(cli.nick)
let handle_command cli iobuf cmd =
@ -161,22 +161,22 @@ let set_nick cli nick =
Hashtbl.replace by_nick nick cli;
cli.nick := nick
let rec handle_command_prereg (nick', username', realname', password') iobuf cmd =
let rec handle_command_prereg (nick, username, realname, password) iobuf cmd =
(* Handle a command during the login phase *)
let acc =
match (Command.as_tuple cmd) with
| (None, "PASS", [password], None) ->
(nick', username', realname', Some password)
| (None, "USER", [username; _; _], Some realname) ->
(nick', Some username, Some (Irc.truncate realname 40), password')
| (None, "NICK", [nick], None) ->
(Some nick, username', realname', password')
| (None, "PASS", [password'], None) ->
(nick, username, realname, Some password')
| (None, "USER", [username'; _; _], Some realname') ->
(nick, Some username', Some (Irc.truncate realname' 40), password)
| (None, "NICK", [nick'], None) ->
(Some nick', username, realname, password)
| _ ->
Iobuf.write iobuf (Command.create
(Some !(Irc.name))
"451" ["*"]
(Some "Register first."));
(nick', username', realname', password')
(nick, username, realname, password)
in
let welcome cli =
try
@ -211,8 +211,8 @@ let rec handle_command_prereg (nick', username', realname', password') iobuf cmd
| _ ->
Iobuf.rebind iobuf (handle_command_prereg acc) ignore
let handle_connection ues grp fd =
let handle_connection d fd addr =
let command_handler = handle_command_prereg (None, None, None, None) in
let close_handler = ignore in
Iobuf.bind ues grp fd command_handler close_handler
Iobuf.bind d fd command_handler close_handler

View File

@ -1,5 +1,5 @@
type t
val write : t -> Command.t -> unit
val handle_connection : Unixqueue.event_system -> Unixqueue.group -> Unix.file_descr -> unit
val handle_connection : Dispatch.t -> Unix.file_descr -> Unix.sockaddr -> unit

View File

@ -39,7 +39,7 @@ let extract_word s =
let rec from_string line =
(* Very simple. Pull out words until you get one starting with ":".
The very first word might start with ":", that doesn't count
because it's the sender.. *)
because it's the sender. *)
let rec loop sender acc line =
let c = (if (line = "") then None else (Some line.[0])) in
match (c, acc) with

View File

@ -1,8 +1,7 @@
(* ==========================================
* I/O buf stuff
*)
type t = {ues: Unixqueue.event_system;
grp: Unixqueue.group;
(* **************************************
* IRC Command I/O buffers
* **************************************)
type t = {d: Dispatch.t;
fd: Unix.file_descr;
outq: Command.t Queue.t;
unsent: string ref;
@ -10,39 +9,24 @@ type t = {ues: Unixqueue.event_system;
ibuf_len: int ref;
addr: string;
command_handler: (t -> Command.t -> unit) ref;
close_handler: (unit -> unit) ref}
close_handler: (string -> unit) ref}
let ibuf_max = 4096
let max_outq = 50
let obuf_max = 4096
let by_file_descr = Hashtbl.create 25
let addr iobuf = iobuf.addr
let write iobuf cmd =
let was_empty = Queue.is_empty iobuf.outq in
Queue.add cmd iobuf.outq;
if (was_empty && (!(iobuf.unsent) = "")) then
Unixqueue.add_resource
iobuf.ues iobuf.grp (Unixqueue.Wait_out iobuf.fd, -.1.0)
Dispatch.modify iobuf.d iobuf.fd [Dispatch.Input; Dispatch.Output]
let close iobuf =
!(iobuf.close_handler) ();
Hashtbl.remove by_file_descr iobuf.fd;
Unix.close iobuf.fd;
Unixqueue.remove_resource iobuf.ues iobuf.grp (Unixqueue.Wait_in iobuf.fd);
try
Unixqueue.remove_resource iobuf.ues iobuf.grp (Unixqueue.Wait_out iobuf.fd);
with Not_found ->
()
let handle_close fd =
try
let iobuf = Hashtbl.find by_file_descr fd in
close iobuf
with Not_found ->
()
let close iobuf message =
!(iobuf.close_handler) message;
Dispatch.delete iobuf.d iobuf.fd;
Unix.close iobuf.fd
let crlf = Str.regexp "\r?\n"
@ -63,27 +47,22 @@ let handle_input iobuf =
in
loop lines
let handle_event ues esys e =
match e with
| Unixqueue.Input_arrived (g, fd) ->
let iobuf = Hashtbl.find by_file_descr fd in
let rec handle_events iobuf fd events =
match events with
| [] ->
()
| Dispatch.Input :: tl ->
let size = ibuf_max - !(iobuf.ibuf_len) in
let len = Unix.read fd iobuf.ibuf !(iobuf.ibuf_len) size in
if (len > 0) then
begin
iobuf.ibuf_len := !(iobuf.ibuf_len) + len;
try
handle_input iobuf
with Not_found ->
if (!(iobuf.ibuf_len) = ibuf_max) then
(* No newline found, and the buffer is full *)
raise (Failure "Buffer overrun");
end
else
close iobuf
| Unixqueue.Output_readiness (g, fd) ->
iobuf.ibuf_len := !(iobuf.ibuf_len) + len;
handle_input iobuf;
if (!(iobuf.ibuf_len) = ibuf_max) then
(* No newline found, and the buffer is full *)
close iobuf "Input buffer overrun"
else
handle_events iobuf fd tl
| Dispatch.Output :: tl ->
(* XXX: Could be optimized to try and fill the output buffer *)
let iobuf = Hashtbl.find by_file_descr fd in
let buf =
if (!(iobuf.unsent) = "") then
let cmd = Queue.pop iobuf.outq in
@ -96,17 +75,19 @@ let handle_event ues esys e =
if n < buflen then
iobuf.unsent := Str.string_after buf n
else if Queue.is_empty iobuf.outq then
Unixqueue.remove_resource ues g (Unixqueue.Wait_out fd)
| Unixqueue.Out_of_band (g, fd) ->
print_endline "oob"
| Unixqueue.Timeout (g, op) ->
print_endline "timeout"
| Unixqueue.Signal ->
print_endline "signal"
| Unixqueue.Extra exn ->
print_endline "extra"
Dispatch.modify iobuf.d fd [Dispatch.Input];
handle_events iobuf fd tl
| Dispatch.Priority :: tl ->
let s = String.create 4096 in
ignore (Unix.recv fd s 0 4096 [Unix.MSG_OOB]);
handle_events iobuf fd tl
| Dispatch.Error :: tl ->
close iobuf "Error"
| Dispatch.Hangup :: tl ->
close iobuf "Hangup"
let bind ues grp fd command_handler close_handler =
let bind d fd command_handler close_handler =
let (outq, unsent, ibuf, ibuf_len) =
(Queue.create (), ref "", String.create ibuf_max, ref 0)
in
@ -117,8 +98,7 @@ let bind ues grp fd command_handler close_handler =
| Unix.ADDR_INET (addr, port) ->
Unix.string_of_inet_addr addr
in
let iobuf = {ues = ues;
grp = grp;
let iobuf = {d = d;
fd = fd;
outq = outq;
unsent = unsent;
@ -128,9 +108,7 @@ let bind ues grp fd command_handler close_handler =
command_handler = ref command_handler;
close_handler = ref close_handler}
in
Hashtbl.replace by_file_descr fd iobuf;
Unixqueue.add_resource ues grp (Unixqueue.Wait_in fd, -.1.0);
Unixqueue.add_close_action ues grp (fd, handle_close)
Dispatch.add d fd (handle_events iobuf) [Dispatch.Input]
let rebind t command_handler close_handler =
t.command_handler := command_handler;

View File

@ -3,7 +3,6 @@ type t
val addr : t -> string
val write : t -> Command.t -> unit
val bind : Unixqueue.event_system -> Unixqueue.group -> Unix.file_descr -> (t -> Command.t -> unit) -> (unit -> unit) -> unit
val rebind: t -> (t -> Command.t -> unit) -> (unit -> unit) -> unit
val close: t -> unit
val handle_event : Unixqueue.event_system -> Unixqueue.event Equeue.t -> Unixqueue.event -> unit
val bind : Dispatch.t -> Unix.file_descr -> (t -> Command.t -> unit) -> (string -> unit) -> unit
val rebind: t -> (t -> Command.t -> unit) -> (string -> unit) -> unit
val close: t -> string -> unit

4
irc.ml
View File

@ -1,10 +1,6 @@
let name = ref "irc.test"
let version = "0.1"
let newline_re = Pcre.regexp "\n\r?"
let argsep_re = Pcre.regexp " :"
let space_re = Pcre.regexp " "
let dbg msg a =
prerr_endline ("[" ^ msg ^ "]");
a

34
ircd.ml
View File

@ -7,32 +7,34 @@ let dbg msg a =
[connection_handler] will be called with the file descriptor of
any new connections.
*)
let establish_server ues connection_handler addr =
let g = Unixqueue.new_group ues in
let handle_event ues esys e =
match e with
| Unixqueue.Input_arrived (g, fd) ->
let establish_server d connection_handler addr =
let rec handle_event fd events =
match events with
| [] ->
()
| Dispatch.Input :: tl ->
let cli_fd, cli_addr = Unix.accept fd in
connection_handler cli_fd
| _ ->
raise Equeue.Reject
connection_handler cli_fd cli_addr;
handle_event fd tl
| Dispatch.Hangup :: tl ->
Dispatch.delete d fd;
handle_event fd tl
| _ :: tl ->
handle_event fd tl
in
let srv = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
Unix.bind srv addr;
Unix.listen srv 50;
Unix.setsockopt srv Unix.SO_REUSEADDR true;
Unixqueue.add_handler ues g handle_event;
Unixqueue.add_resource ues g (Unixqueue.Wait_in srv, -.1.0)
Dispatch.add d fd handle_event [Dispatch.Input];
let main () =
let ues = Unixqueue.create_unix_event_system () in
let g = Unixqueue.new_group ues in
Unixqueue.add_handler ues g Iobuf.handle_event;
let d = Dispatch.create 50 in
establish_server
ues
Client.handle_connection
(Unix.ADDR_INET (Unix.inet_addr_any, 7777));
ues#run ()
(Client.handle_connection d)
(Unix.ADDR_INET (Unix.inet_addr_any, 6667));
Dispatch.run d
let _ =
main ()

107
tests.ml
View File

@ -1,6 +1,4 @@
open Unixqueue
open OUnit
open Irc
let dump x =
Printf.ksprintf (fun str -> prerr_string str; flush stderr) x
@ -88,29 +86,29 @@ let chat d fd s =
let ibuf = Buffer.create 4096 in
let handle_timer _ =
failwith (Printf.sprintf "fd=%d timeout waiting for %s"
(int_of_file_descr fd)
(string_of_chat_event (List.hd !script)))
(int_of_file_descr fd)
(string_of_chat_event (List.hd !script)))
in
let nomatch got =
failwith (Printf.sprintf "fd=%d expecting %s\n got %s"
(int_of_file_descr fd)
(string_of_chat_event (List.hd !script))
(String.escaped got))
(int_of_file_descr fd)
(string_of_chat_event (List.hd !script))
(String.escaped got))
in
let rec run_script fd =
match !script with
| [] ->
if ((Buffer.length obuf) = 0) then begin
if ((Buffer.length obuf) = 0) then begin
Dispatch.delete_timer d timer;
(try
Dispatch.delete d fd
with (Failure _) ->
());
(try
Dispatch.delete d fd
with (Failure _) ->
());
Unix.close fd
end
end
| Send buf :: tl ->
Buffer.add_string obuf buf;
Dispatch.modify d fd [Dispatch.Input; Dispatch.Output];
Dispatch.modify d fd [Dispatch.Input; Dispatch.Output];
script := tl;
run_script fd
| Recv buf :: tl ->
@ -127,8 +125,8 @@ let chat d fd s =
((String.length ibuf_str) - buf_len);
run_script fd
end else
nomatch ibuf_str
end else
nomatch ibuf_str
end else
()
| Regex buf :: tl ->
let ibuf_str = Buffer.contents ibuf in
@ -145,7 +143,7 @@ let chat d fd s =
((String.length ibuf_str) - match_len);
run_script fd
else
nomatch ibuf_str
nomatch ibuf_str
else
()
@ -159,25 +157,25 @@ let chat d fd s =
let n = Unix.read fd s 0 4096 in
Buffer.add_substring ibuf s 0 n;
run_script fd;
handler fd tl
handler fd tl
| Dispatch.Output :: tl ->
begin
begin
if ((Buffer.length obuf) = 0) then
Dispatch.modify d fd [Dispatch.Input]
else
let ostr = Buffer.contents obuf in
let olen = Buffer.length obuf in
let n = Unix.write fd ostr 0 olen in
Buffer.clear obuf;
Buffer.add_substring obuf ostr n (olen - n)
end;
handler fd tl
Buffer.clear obuf;
Buffer.add_substring obuf ostr n (olen - n)
end;
handler fd tl
| Dispatch.Hangup :: tl ->
(* Stop listening to this fd, it will always return Hangup *)
(try
Dispatch.delete d fd
with (Failure _) ->
())
(* Stop listening to this fd, it will always return Hangup *)
(try
Dispatch.delete d fd
with (Failure _) ->
())
| _ ->
failwith "Unexpected event"
in
@ -258,7 +256,7 @@ let unit_tests =
last_timer := time
in
let s = String.create 4096 in
let s = String.create 4096 in
assert_equal 8 (Unix.write a "dispatch" 0 8);
Dispatch.add d b handle [Dispatch.Input; Dispatch.Output];
@ -275,15 +273,15 @@ let unit_tests =
Dispatch.once d;
assert_equal ~printer:string_of_float 0.0 !last_timer;
Dispatch.modify d b [Dispatch.Input];
Dispatch.once d;
if (!last_timer = 0.0) then
(* Give it one chance *)
Dispatch.once d;
Dispatch.modify d b [Dispatch.Input];
Dispatch.once d;
if (!last_timer = 0.0) then
(* Give it one chance *)
Dispatch.once d;
assert_equal ~printer:string_of_float time !last_timer;
Dispatch.modify d b [Dispatch.Input; Dispatch.Output];
assert_equal 6 (Unix.write a "gnarly" 0 6);
Dispatch.modify d b [Dispatch.Input; Dispatch.Output];
assert_equal 6 (Unix.write a "gnarly" 0 6);
Dispatch.once d;
assert_equal (b, [Dispatch.Input; Dispatch.Output]) !last_event;
assert_equal 6 (Unix.read b s 0 4096);
@ -335,7 +333,6 @@ let unit_tests =
);
]
(*
let do_login nick =
[
Send ("USER " ^ nick ^ " +iw " ^ nick ^ " :gecos\r\n");
@ -378,12 +375,11 @@ let regression_tests =
Recv ":testserver.test 401 nick otherguy :No such nick/channel\r\n";
]
in
let g = Unixqueue.new_group ues in
let d = Dispatch.create 2 in
let a,b = Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0 in
Unixqueue.add_handler ues g Iobuf.handle_event;
Client.handle_connection ues g a;
ignore (new chat_handler script ues b);
chat_run ues);
Client.handle_connection d a (Unix.getpeername a);
chat d b script;
Dispatch.run d);
"Second connection" >::
(fun () ->
@ -394,12 +390,11 @@ let regression_tests =
Recv ":testserver.test 303 otherguy :otherguy\r\n";
]
in
let g = Unixqueue.new_group ues in
let d = Dispatch.create 2 in
let a,b = Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0 in
Unixqueue.add_handler ues g Iobuf.handle_event;
Client.handle_connection ues g a;
ignore (new chat_handler script ues b);
chat_run ues);
Client.handle_connection d a (Unix.getpeername a);
chat d b script;
Dispatch.run d);
"Simultaneous connections" >::
(fun () ->
@ -421,20 +416,16 @@ let regression_tests =
Recv ":alice!alice@UDS PRIVMSG bob :Hi Bob!\r\n";
]
in
let g = Unixqueue.new_group ues in
let d = Dispatch.create 4 in
let aa,ab = Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0 in
let ba,bb = Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0 in
Unixqueue.add_handler ues g Iobuf.handle_event;
Client.handle_connection ues g aa;
Client.handle_connection ues g ba;
ignore (new chat_handler script1 ues ab);
ignore (new chat_handler script2 ues bb);
chat_run ues);
Client.handle_connection d aa (Unix.getpeername aa);
Client.handle_connection d ba (Unix.getpeername ba);
chat d ab script1;
chat d bb script2;
Dispatch.run d);
]
*)
let _ =
Irc.name := "testserver.test";
run_test_tt_main (TestList [unit_tests (*; regression_tests *)])
run_test_tt_main (TestList [unit_tests; regression_tests])