Event Horizon: Asynchronous I/O for the J* language
Event Horizon extends J* with asynchronous I/O, letting you write concurrent network and I/O code
using an async/await-like syntax. Under the hood, the library drives a single-threaded
libuv event loop that multiplexes I/O readiness events and schedules callbacks
when data is available.
async/await: coroutine-based concurrency via the@asyncdecorator andyield- Promises:
Promise,all(),race(),asResolved(),asRejected() - TCP:
TCPStreamfor clients and servers, with automatic DNS resolution - TLS:
TLSStreamfor encrypted TCP connections (client and server), backed by mbedTLS - UDP:
UDPSocket, including multicast support - Pipes:
PipeStreamfor cross-platform pipes (Unix domain sockets on Unix, named pipes on Windows) - Timers:
wait(),setTimeout(),setInterval(),nextTick() - DNS: asynchronous
getAddrInfo() - Raw libuv bindings:
event_horizon.uvfor callback-style code
Event Horizon uses J*'s generator protocol to simulate async/await. Marking a function with the
@async decorator causes it to return a Promise instead of a plain value. Inside such a
function, yielding a Promise suspends the coroutine until that promise settles: if it
fulfills, execution resumes with the resolved value; if it rejects, the rejection is re-raised
as an ordinary exception that can be caught with try/except.
The event loop is started explicitly by passing the top-level Promise to evh.run(), which
blocks until all pending asynchronous operations have completed.
| Module | Exports |
|---|---|
event_horizon |
run() |
event_horizon.async |
async |
event_horizon.promise |
Promise, asResolved, asRejected, all, race |
event_horizon.tcp |
TCPStream |
event_horizon.tls |
TLSStream |
event_horizon.udp |
UDPSocket |
event_horizon.pipe |
PipeStream |
event_horizon.timers |
wait, waitOneTick, setTimeout, setInterval, nextTick |
event_horizon.dns |
getAddrInfo |
event_horizon.uv |
Raw libuv bindings |
The following snippet implements a TCP echo server. Error handling is omitted for brevity; see the
examples/ folder for complete, production-style examples.
import event_horizon as evh
import event_horizon.async for async
import event_horizon.tcp for TCPStream
// The `@async` decorator turns this function into a coroutine that returns a Promise.
// Inside it, `yield` suspends execution until the awaited Promise settles.
@async
fun handleClient(client)
var data
while data = yield client.readLine()
yield client.write(data)
end
end
@async
fun main()
var server = TCPStream()
server.bind("0.0.0.0", 8080)
// `listen` returns a Promise that resolves when the server stops accepting connections.
yield server.listen(handleClient)
end
// Start the event loop. Passing the Promise lets evh.run() attach an error handler
// so any unhandled rejection prints a stack trace instead of silently disappearing.
evh.run(main())
While async/await is the default and preferred way to write asynchronous code with Event Horizon,
the event_horizon.uv module exposes thin, callback-based wrappers around libuv for cases where
direct control over the event loop is needed.
- J* ≥ 1.0
- CMake ≥ 3.10
- libuv ≥ 1.0 (or use the bundled build described below)
- mbedTLS ≥ 3.0 (or use the bundled build described below)
cmake -B build
cmake --build build -j
sudo cmake --install buildThen import the library in your J* code:
import event_horizon as evh
Both libuv and mbedTLS can be downloaded and compiled as part of the build, without requiring them to be installed on your system:
cmake -B build -DEVH_VENDOR_LIBUV=ON -DEVH_VENDOR_MBEDTLS=ON
cmake --build build -j
sudo cmake --install buildThe options are independent - you can vendor either library individually if the other is already available system-wide.
jstar tests/run.jsrThe TLS tests (tests/evh/tls.jsr, tests/uv/tls.jsr) require a private key that
is not stored in the repository. Before running the full suite for the first time,
generate a self-signed certificate and key in tests/certs/:
openssl req -x509 -newkey rsa:2048 \
-keyout tests/certs/key.pem \
-out tests/certs/cert.pem \
-days 3650 -nodes \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,digitalSignature,keyEncipherment" \
-addext "extendedKeyUsage=serverAuth"