aboutsummaryrefslogtreecommitdiffhomepage
path: root/notes/event_loops.txt
blob: c75558dde886d105388442cbc383651aa34d77d9 (plain)
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186

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<string> dir_cfg
    }

    type Server = struct {
        own var WaitAll<Server> 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<String> 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<String> entry) -> f.open().contents_to_slot(entry)
        } after {
            server.tls_sock = .new(.listen_local(80, ServerState))
        }
        return server
    }