Centralized events loops ======================== [green threads, if feasible to implement, would be a lot easier to use...] It would be nice to have some kind of centralized event loop. Requirements: * Multiple places of one linkage unit may need to get events * Multiple places of one application (executable + libraries) may need to get events. * Different types of events need to be possible to register: - files - sockets - timers (- signals) - internal events - system specific events (like window messages on Windows) * Unrelated events should be isolated from event handlers that shouldn't see them. * An event handler should be able to look ahead and priotize events internally. * Events from different sources need to be handled in a round robin way, but some may have higher priority in terms of fraction of requests or fraction of time. * Events related to the same object (e.g. a socket) should generally not happen in parallel (except possibly for incoming connections) Events may have some special syntax, but there also needs to be an API in some kind of SLUL runtime library. The API should not be locked to any specific type of event handling (polling, ring buffer, etc). At startup of a program it should be possible to request a specific implementation. If no request is made, the runtime library should lazilly select some good all-round implementation. API idea -------- Internal API: Each module that uses events exposes a function: func MODNAME_event_wakeup(ref EventCtx evctx) The runtime library exposes a set of internal functions: ## Creates an event context func .new() -> ref var own EventPool func EventPool.subsc_file(ref grab var File file) var -> ref grab(file) EventTicket or Error func EventPool.subsc_socket(ref grab var Socket sock) var -> ref grab(sock) EventTicket or Error func EventPool.subsc_timer(ref grab var Timer timer) var -> ref grab(timer) EventTicket or Error func EventPool.unsubsc_file(ref release var File file, ref grab(file) EventTicket ticket) var func EventPool.unsubsc_socket(ref release var Socket sock, ref grab(sock) EventTicket ticket) var func EventPool.unsubsc_timer(ref release var Timer timer, ref grab(timer) EventTicket ticket) var ## Synchronizes all changes to the EventCtx func EventPool.sync() var ## Waits for events in a loop func EventPool.loop() var func EventPool.free() var own Try 2: ## Creates an event context func .new() -> ref var own EventPool # ticket is a special 0-byte type. It has only one value, which is 1. # tickets must eventually be returned. func EventPool.subsc_file(grabref var File file, ref T prm) var -> ticket(file) or Error func EventPool.subsc_single_socket(grabref var Socket sock, ref T prm) var -> ticket(sock) or Error func EventPool.subsc_connections(grabref var Socket sock, ref T prm) var -> ticket(sock) or Error func EventPool.subsc_timer(grabref var Timer timer, ref T prm) var -> ticket(timer) or Error func EventPool.unsubsc_file(releaseref var File file, ticket(file) t) var func EventPool.unsubsc_single_socket(releaseref var Socket sock, ticket(sock) t) var func EventPool.unsubsc_connections(releaseref var Socket sock, ticket(sock) t) var func EventPool.unsubsc_timer(releaseref var Timer timer, ticket(timer) t) var ## Synchronizes all changes to the EventPool. ## This happens automatically when calling (or returning to) the loop function. func EventPool.sync() var ## Waits for events in a loop func EventPool.loop() var func EventPool.free() var own ... High level API example: event .ready(grabref SecureSocket server) -> ref own var ServerState { ... } event ServerState.start_failed(ref SecureSocket server) static { ... } event ServerState.shutting_down() { ... } event ServerState.got_conn(ref SecureSocket server) -> ref own var Connection { own var Connection conn = .new() conn.socket = server.accept(Connection) ... return conn } event Connection.closed_conn() own var { ... } event Connection.has_data() { ... } event Connection.can_send() { ... } type ConfigData = struct { string main_cfg List dir_cfg } type Server = struct { own var WaitAll wait own var threaded ConfigData cfg } func .new() -> own var Server { own var Server server = .new() server.wait = .wait(server) ... return server } event Server.wait_done() { # TODO how to handle out of memory? # Asynchronously creates a socket and reads system truststores #own SecureSocket ssock = .new(.listen_local(80, ServerState)) server.tls_sock = .new(.listen_local(80, ServerState)) } func start_server() -> own var Server { own var ConfigData cfg = .new() # server takes ownership of cfg, but cfg is implictly grabbed # by this function, because it uses cfg own var Server server = .new(cfg) # wait grabs server #own var WaitAll wait = .new(server) server.wait.add(File.open("/etc/myserver/config.cfg").contents_to_string(@cfg.main_cfg)) server.wait.add(File.list("/etc/myserver/config.d/*.conf").add_to_list(@cfg.dir_cfg, lambda (File f, Slot entry) -> f.open().contents_to_slot(entry)) server.wait.start(server) # cfg is ungrabbed return server } It would be cool if we could write: func start_server() -> own var Server { own var ConfigData cfg = .new() own var Server server = .new(cfg) async { File.open("/etc/myserver/config.cfg").contents_to_string(@cfg.main_cfg) File.list("/etc/myserver/config.d/*.conf").add_to_list(@cfg.dir_cfg, lambda (File f, Slot entry) -> f.open().contents_to_slot(entry) } after { server.tls_sock = .new(.listen_local(80, ServerState)) } return server }