Chris@63: // Copyright (c) 2016 Sandstorm Development Group, Inc. and contributors Chris@63: // Licensed under the MIT License: Chris@63: // Chris@63: // Permission is hereby granted, free of charge, to any person obtaining a copy Chris@63: // of this software and associated documentation files (the "Software"), to deal Chris@63: // in the Software without restriction, including without limitation the rights Chris@63: // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell Chris@63: // copies of the Software, and to permit persons to whom the Software is Chris@63: // furnished to do so, subject to the following conditions: Chris@63: // Chris@63: // The above copyright notice and this permission notice shall be included in Chris@63: // all copies or substantial portions of the Software. Chris@63: // Chris@63: // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR Chris@63: // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, Chris@63: // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE Chris@63: // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER Chris@63: // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, Chris@63: // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN Chris@63: // THE SOFTWARE. Chris@63: Chris@63: #ifndef KJ_ASYNC_WIN32_H_ Chris@63: #define KJ_ASYNC_WIN32_H_ Chris@63: Chris@63: #if !_WIN32 Chris@63: #error "This file is Windows-specific. On Unix, include async-unix.h instead." Chris@63: #endif Chris@63: Chris@63: #include "async.h" Chris@63: #include "time.h" Chris@63: #include "io.h" Chris@63: #include Chris@63: #include Chris@63: Chris@63: // Include windows.h as lean as possible. (If you need more of the Windows API for your app, Chris@63: // #include windows.h yourself before including this header.) Chris@63: #define WIN32_LEAN_AND_MEAN 1 Chris@63: #define NOSERVICE 1 Chris@63: #define NOMCX 1 Chris@63: #define NOIME 1 Chris@63: #include Chris@63: #include "windows-sanity.h" Chris@63: Chris@63: namespace kj { Chris@63: Chris@63: class Win32EventPort: public EventPort { Chris@63: // Abstract base interface for EventPorts that can listen on Win32 event types. Due to the Chris@63: // absurd complexity of the Win32 API, it's not possible to standardize on a single Chris@63: // implementation of EventPort. In particular, there is no way for a single thread to use I/O Chris@63: // completion ports (the most efficient way of handling I/O) while at the same time waiting for Chris@63: // signalable handles or UI messages. Chris@63: // Chris@63: // Note that UI messages are not supported at all by this interface because the message queue Chris@63: // is implemented by user32.dll and we want libkj to depend only on kernel32.dll. A separate Chris@63: // compat library could provide a Win32EventPort implementation that works with the UI message Chris@63: // queue. Chris@63: Chris@63: public: Chris@63: // --------------------------------------------------------------------------- Chris@63: // overlapped I/O Chris@63: Chris@63: struct IoResult { Chris@63: DWORD errorCode; Chris@63: DWORD bytesTransferred; Chris@63: }; Chris@63: Chris@63: class IoOperation { Chris@63: public: Chris@63: virtual LPOVERLAPPED getOverlapped() = 0; Chris@63: // Gets the OVERLAPPED structure to pass to the Win32 I/O call. Do NOT modify it; just pass it Chris@63: // on. Chris@63: Chris@63: virtual Promise onComplete() = 0; Chris@63: // After making the Win32 call, if the return value indicates that the operation was Chris@63: // successfully queued (i.e. the completion event will definitely occur), call this to wait Chris@63: // for completion. Chris@63: // Chris@63: // You MUST call this if the operation was successfully queued, and you MUST NOT call this Chris@63: // otherwise. If the Win32 call failed (without queuing any operation or event) then you should Chris@63: // simply drop the IoOperation object. Chris@63: // Chris@63: // Dropping the returned Promise cancels the operation via Win32's CancelIoEx(). The destructor Chris@63: // will wait for the cancellation to complete, such that after dropping the proimse it is safe Chris@63: // to free the buffer that the operation was reading from / writing to. Chris@63: // Chris@63: // You may safely drop the `IoOperation` while still waiting for this promise. You may not, Chris@63: // however, drop the `IoObserver`. Chris@63: }; Chris@63: Chris@63: class IoObserver { Chris@63: public: Chris@63: virtual Own newOperation(uint64_t offset) = 0; Chris@63: // Begin an I/O operation. For file operations, `offset` is the offset within the file at Chris@63: // which the operation will start. For stream operations, `offset` is ignored. Chris@63: }; Chris@63: Chris@63: virtual Own observeIo(HANDLE handle) = 0; Chris@63: // Given a handle which supports overlapped I/O, arrange to receive I/O completion events via Chris@63: // this EventPort. Chris@63: // Chris@63: // Different Win32EventPort implementations may handle this in different ways, such as by using Chris@63: // completion routines (APCs) or by using I/O completion ports. The caller should not assume Chris@63: // any particular technique. Chris@63: // Chris@63: // WARNING: It is only safe to call observeIo() on a particular handle once during its lifetime. Chris@63: // You cannot observe the same handle from multiple Win32EventPorts, even if not at the same Chris@63: // time. This is because the Win32 API provides no way to disassociate a handle from an I/O Chris@63: // completion port once it is associated. Chris@63: Chris@63: // --------------------------------------------------------------------------- Chris@63: // signalable handles Chris@63: // Chris@63: // Warning: Due to limitations in the Win32 API, implementations of EventPort may be forced to Chris@63: // spawn additional threads to wait for signaled objects. This is necessary if the EventPort Chris@63: // implementation is based on I/O completion ports, or if you need to wait on more than 64 Chris@63: // handles at once. Chris@63: Chris@63: class SignalObserver { Chris@63: public: Chris@63: virtual Promise onSignaled() = 0; Chris@63: // Returns a promise that completes the next time the handle enters the signaled state. Chris@63: // Chris@63: // Depending on the type of handle, the handle may automatically be reset to a non-signaled Chris@63: // state before the promise resolves. The underlying implementaiton uses WaitForSingleObject() Chris@63: // or an equivalent wait call, so check the documentation for that to understand the semantics. Chris@63: // Chris@63: // If the handle is a mutex and it is abandoned without being unlocked, the promise breaks with Chris@63: // an exception. Chris@63: Chris@63: virtual Promise onSignaledOrAbandoned() = 0; Chris@63: // Like onSingaled(), but instead of throwing when a mutex is abandoned, resolves to `true`. Chris@63: // Resolves to `false` for non-abandoned signals. Chris@63: }; Chris@63: Chris@63: virtual Own observeSignalState(HANDLE handle) = 0; Chris@63: // Given a handle that supports waiting for it to become "signaled" via WaitForSingleObject(), Chris@63: // return an object that can wait for this state using the EventPort. Chris@63: Chris@63: // --------------------------------------------------------------------------- Chris@63: // APCs Chris@63: Chris@63: virtual void allowApc() = 0; Chris@63: // If this is ever called, the Win32EventPort will switch modes so that APCs can be scheduled Chris@63: // on the thread, e.g. through the Win32 QueueUserAPC() call. In the future, this may be enabled Chris@63: // by default. However, as of this writing, Wine does not support the necessary Chris@63: // GetQueuedCompletionStatusEx() call, thus allowApc() breaks Wine support. (Tested on Wine Chris@63: // 1.8.7.) Chris@63: // Chris@63: // If the event port implementation can't support APCs for some reason, this throws. Chris@63: Chris@63: // --------------------------------------------------------------------------- Chris@63: // time Chris@63: Chris@63: virtual Timer& getTimer() = 0; Chris@63: }; Chris@63: Chris@63: class Win32WaitObjectThreadPool { Chris@63: // Helper class that implements Win32EventPort::observeSignalState() by spawning additional Chris@63: // threads as needed to perform the actual waiting. Chris@63: // Chris@63: // This class is intended to be used to assist in building Win32EventPort implementations. Chris@63: Chris@63: public: Chris@63: Win32WaitObjectThreadPool(uint mainThreadCount = 0); Chris@63: // `mainThreadCount` indicates the number of objects the main thread is able to listen on Chris@63: // directly. Typically this would be zero (e.g. if the main thread watches an I/O completion Chris@63: // port) or MAXIMUM_WAIT_OBJECTS (e.g. if the main thread is a UI thread but can use Chris@63: // MsgWaitForMultipleObjectsEx() to wait on some handles at the same time as messages). Chris@63: Chris@63: Own observeSignalState(HANDLE handle); Chris@63: // Implemetns Win32EventPort::observeSignalState(). Chris@63: Chris@63: uint prepareMainThreadWait(HANDLE* handles[]); Chris@63: // Call immediately before invoking WaitForMultipleObjects() or similar in the main thread. Chris@63: // Fills in `handles` with the handle pointers to wait on, and returns the number of handles Chris@63: // in this array. (The array should be allocated to be at least the size passed to the Chris@63: // constructor). Chris@63: // Chris@63: // There's no need to call this if `mainThreadCount` as passed to the constructor was zero. Chris@63: Chris@63: bool finishedMainThreadWait(DWORD returnCode); Chris@63: // Call immediately after invoking WaitForMultipleObjects() or similar in the main thread, Chris@63: // passing the value returend by that call. Returns true if the event indicated by `returnCode` Chris@63: // has been handled (i.e. it was WAIT_OBJECT_n or WAIT_ABANDONED_n where n is in-range for the Chris@63: // last call to prepareMainThreadWait()). Chris@63: }; Chris@63: Chris@63: class Win32IocpEventPort final: public Win32EventPort { Chris@63: // An EventPort implementation which uses Windows I/O completion ports to listen for events. Chris@63: // Chris@63: // With this implementation, observeSignalState() requires spawning a separate thread. Chris@63: Chris@63: public: Chris@63: Win32IocpEventPort(); Chris@63: ~Win32IocpEventPort() noexcept(false); Chris@63: Chris@63: // implements EventPort ------------------------------------------------------ Chris@63: bool wait() override; Chris@63: bool poll() override; Chris@63: void wake() const override; Chris@63: Chris@63: // implements Win32IocpEventPort --------------------------------------------- Chris@63: Own observeIo(HANDLE handle) override; Chris@63: Own observeSignalState(HANDLE handle) override; Chris@63: Timer& getTimer() override { return timerImpl; } Chris@63: void allowApc() override { isAllowApc = true; } Chris@63: Chris@63: private: Chris@63: class IoPromiseAdapter; Chris@63: class IoOperationImpl; Chris@63: class IoObserverImpl; Chris@63: Chris@63: AutoCloseHandle iocp; Chris@63: AutoCloseHandle thread; Chris@63: Win32WaitObjectThreadPool waitThreads; Chris@63: TimerImpl timerImpl; Chris@63: mutable std::atomic sentWake {false}; Chris@63: bool isAllowApc = false; Chris@63: Chris@63: static TimePoint readClock(); Chris@63: Chris@63: void waitIocp(DWORD timeoutMs); Chris@63: // Wait on the I/O completion port for up to timeoutMs and pump events. Does not advance the Chris@63: // timer; caller must do that. Chris@63: Chris@63: bool receivedWake(); Chris@63: Chris@63: static AutoCloseHandle newIocpHandle(); Chris@63: static AutoCloseHandle openCurrentThread(); Chris@63: }; Chris@63: Chris@63: } // namespace kj Chris@63: Chris@63: #endif // KJ_ASYNC_WIN32_H_