This page describes a proposal for a standard D framework/API for event loops and handling events.
- 1 Motivation
- 2 Existing work
- 3 Goals
- 4 Prerequisites
- 5 Design
- 6 Plan
- 7 Implementation notes
- 8 Questions
- Allow writing high-performance network programs in D, which match the performance of existing native-code solutions
- Allow multiple networking libraries / web frameworks to co-exist, by removing the requirement that each library implements its own event loop / protocol implementation / etc.
- Strengthen the D standard library by introducing any missing prerequisites
- Provide the requirements for implementing network protocols in the D standard library
- In the long run, allow D to compete with Node.js / Go for network applications, without relying on third-party libraries / dependencies
The Vibe.d framework
Vibe.d (http://vibed.org/) is currently the most popular solution for writing web applications in D. Vibe uses an asynchronous model and a fiber-based API, exclusively.
Vibe was designed and developed for web applications specifically in mind, and thus it is somewhat HTTP-server-centric and difficult to use for certain other types of network applications.
- Until recently, it was not possible to decouple reading and writing from a socket, thus it wasn't possible to write to a socket that was waiting for data - which is required for ad-hoc two-way communication.
- Although it is now possible, it requires some explicit management of connection/stream ownership (example program).
- Problems with implementing network client applications, due to event loops not exiting automatically (caused by abstract-event-loop library design defects, and Vibe's stabilized API) - Discussion: 1 2
- Explicit management of connection ownership makes ad-hoc cross-connection/cross-task communication somewhat onerous
- The exclusive use of the blocking fiber API makes certain tasks difficult, such as waiting for multiple events simultaneously.
ae (https://github.com/CyberShadow/ae) is a generic D library, which also includes support for asynchronous networking. Originally written in D1, it mostly uses an OOP API. It can use select() and libevent. It is notable mainly because it was used in multiple diverse network applications, most notably DFeed - a web frontend, NNTP/IRC/REST client, TCP server, and RSS aggregator.
Both libraries also contain miscellaneous code which is not specific to networking / event loops:
- Memory management: vibe.utils.memory, ae.sys.data / ae.utils.alloc
- Efficient containers: vibe.utils.array / vibe.utils.hashmap, ae.utils.container
In a few cases, they are reimplementations of Phobos modules, presumably because the Phobos code was lacking.
This indicates a necessity to improve the Phobos implementations.
- Third-party additions exist to allow using multiple (isolated) threads, as well as a fiber-based model.
- Multi-threaded (take advantage of D concurrency capabilities)
- Clean, structured API (e.g. layers: abstract event loop, network, protocol)
- Do not limit the API to network events - provide abstractions that allow implementing event loops for other systems, e.g. SDL
- Allow multiple event loops (e.g. a network and an SDL event loop), and facilitate such usage (e.g. by allowing to designate one interruptible master event loop, and satellite event loops which forward events to the master event loop thread)
- Allow using both a synchronous (single-task or fiber) API or an asynchronous (callback / delegate-based) API (one API could be implemented as a layer on top of the other)
- Do not limit this to network events either. For example, allow implementing a Windows event object event loop, which provides a call equivalent to WaitForSingleObject, which also uses a synchronous API, but instead of blocking the current thread, yields the current fiber and resumes it from the main event loop.
- Support native OS high-performance event loop capabilities, such as Input/Output Completion Ports and Grand Central Dispatch
- Load abstracted-event-loop libraries (libevent, libev) dynamically (to allow dynamic fallback at runtime and avoid static linking dependencies)
- Allow using the blocking API without an event loop at all (that is, no asynchronicity at all - use blocking calls)
- Can be used to make one simple client connection, or a one-thread-per-connection server model (although the latter shouldn't be encouraged)
- Design and implementation must satisfy the requirements of existing D frameworks / network applications (e.g. Vibe / DFeed)
- Dynamic loading of libraries
- Manual memory management (custom allocators) may be required to match Vibe performance
API blocks are interfaces which indicate that an event loop supports waiting on certain kinds of events.
- Sockets (network communication)
- Files (disk access)
- DNS lookups
- Directory change notifications
- Idle event (signaled when no other events are in the queue)
- Cross-thread event (interruptible event loop that can be asked to stop from another thread, and execute a function/delegate immediately)
API providers implement the API block interfaces.
Aside from event loops (which are API providers), some default ones can be:
- Straight-forward blocking calls (no asynchronicity)
- Threaded - auto-generated wrapper around a blocking provider, which runs blocking calls in a thread pool
- Example: Not every event loop implementation can be capable of performing asynchronous DNS lookup. Thus, the lookup would need to happen either synchronously (by blocking the calling thread), or in a separate thread.
Extending event loop APIs
It should be possible to "plug in" an API provider into an existing event loop, without the event loop code knowing about the API provider. The event loop would simply provide a private interface through which it can be extended.
- Socket events can be added unto a standard Windows message loop
- SDL can issue network events through its main event loop when SDL_net is used, but the SDL event loop code shouldn't know anything about SDL_net specifically
- Interruptability can be added to an uninterruptible network event loop (such as select()) by using socketpair
Included event loops
Aside from wrappers around abstract-event-loop libraries, here's some event loops that can be included with the implementation:
- std.socket.select() (use std.socket)
- Covers a lot of common cases, has no additional requirements, and is very portable
- Thread.sleep (timers only)
- If the main event loop does not provide a timer API block, but is interruptible, the timer event loop can be run in its own thread.
- std.concurrency.receive (cross-thread event only)
- Multiple non-interruptible event loops can be "joined" by having each in its own thread, and having the std.concurrency event loop as the main one.
- Establish requirements
- Draft API, implementation
- Port an existing network application which makes versatile use of networking (nomination: DFeed)
- Avoid hacks and workarounds, as these indicate a deficiency in the API.
- Submit for inclusion in Phobos/Druntime
- See libeio for asynchronous disk access
- Some high-performance event loops which operate with thread pools, such as IOCP, select threads to handle an event arbitrarily. This can cause tasks that begin waiting for an event to resume in another thread, and thus "see" other data in its TLS (global and static variables).
- WinRT support? Mentioned as a Vibe requirement. Can D target WinRT at all?
- std.event or core.event?