| // Copyright 2019 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #ifndef IREE_BASE_WAIT_HANDLE_H_ |
| #define IREE_BASE_WAIT_HANDLE_H_ |
| |
| #include <atomic> |
| #include <cstdint> |
| #include <string> |
| #include <utility> |
| |
| #include "absl/time/clock.h" |
| #include "absl/time/time.h" |
| #include "absl/types/span.h" |
| #include "iree/base/ref_ptr.h" |
| #include "iree/base/status.h" |
| #include "iree/base/time.h" |
| |
| namespace iree { |
| |
| // Interfaces for waitable objects that can produce WaitHandles. |
| // WaitableObjects are much like ::thread::Selectable, only they support both |
| // the classic locking style as well as file descriptors for use with select(). |
| // |
| // Usage: |
| // class MyWaitableObject : public WaitableObject { |
| // public: |
| // std::string DebugString() const override { return "something useful"; } |
| // WaitHandle OnAsyncTask() { |
| // return WaitHandle(retain_ref(this)); |
| // } |
| // private: |
| // StatusOr<std::pair<FdType, int>> AcquireFdForWait( |
| // absl::Time deadline) override { |
| // // If blocking traditionally do so now and then return this: |
| // return std::make_pair(FdType::kPermanent, kSignaledFd); |
| // // Otherwise, see ManualResetEvent for an example using fds. |
| // } |
| // StatusOr<bool> TryResolveWakeOnFd(int fd) override { |
| // // Return true iff the object is really acquired, such as the semaphore |
| // // being decremented. |
| // return true; |
| // } |
| // }; |
| class WaitableObject : public RefObject<WaitableObject> { |
| public: |
| // Indicates that a file descriptor is invalid. It will not block when waited |
| // upon. |
| constexpr static int kInvalidFd = -1; |
| // Indicates that a file descriptor should be treated as signaled. |
| // Waiting on this fd should return as if it has already been signaled. |
| constexpr static int kSignaledFd = -2; |
| |
| // Defines the type of the native handle used for synchronization. |
| enum class FdType : uint16_t { |
| // Event has no handle and should be treated as permanently signaled. |
| kPermanent, |
| |
| // Android/Linux/iOS-compatible POSIX pipe handle. |
| // Two handles are generated: one for transmitting and one for receiving. |
| // |
| // More information: |
| // http://man7.org/linux/man-pages/man2/pipe.2.html |
| kPipe, |
| |
| // Android/Linux eventfd handle. |
| // These are akin to pipe() but require only a single handle and have |
| // significantly lower overhead (equivalent if not slightly better than |
| // pthreads condvars). |
| // |
| // eventfds support acting as both semaphores and auto reset events. |
| // |
| // More information: |
| // http://man7.org/linux/man-pages/man2/eventfd.2.html |
| kEventFd, |
| |
| // Android/Linux sync_file handle (aka 'sync fence'). |
| // The handle is allocated indirectly by the device driver via the |
| // <linux/sync_file.h> API. It may be waited upon with poll(), select(), or |
| // epoll() and must be closed with close() when no longer required. If |
| // waiting on multiple sync_files the caller should first merge them |
| // together. |
| // |
| // A sync_file must only be used as fences (one-shot manual reset events). |
| // |
| // More information: |
| // https://www.kernel.org/doc/Documentation/sync_file.txt |
| // https://lwn.net/Articles/702339/ |
| // https://source.android.com/devices/graphics/implement-vsync#explicit_synchronization |
| kSyncFile, |
| }; |
| |
| virtual ~WaitableObject() = default; |
| |
| // Returns a string representing the object, either specified as a debug_name |
| // or a unique ID. |
| virtual std::string DebugString() const = 0; |
| |
| // Attempts to acquire a file descriptor for the waitable objects by the given |
| // |deadline|. In many cases this will return immediately with a valid fd. |
| // |
| // In cases where the file descriptor may not be available the call may block |
| // until either it is available or the |deadline| has elapsed. Use |
| // absl::InfinitePast() to prevent blocking. |
| // |
| // Returns a valid file descriptor or kInvalidFd as an indication that the |
| // object should not be waited on (already signaled, etc). Can return |
| // kSignaledFd to indicate that it's already known that the handle has been |
| // signaled and the caller should resolve as if it caused a wake normally. |
| virtual StatusOr<std::pair<FdType, int>> AcquireFdForWait( |
| absl::Time deadline) = 0; |
| |
| // Tries to resolve the object with the given |fd|. |
| // In many cases this will no-op, however some types may require additional |
| // checks to ensure that the wait operation succeeded (such as semaphores |
| // that may need to query a count). If resolution fails the waitable object |
| // must not be considered signaled. This call will never block. |
| virtual StatusOr<bool> TryResolveWakeOnFd(int fd) = 0; |
| }; |
| |
| // Handle to waitable objects. |
| // WaitHandles are created by a particular synchronization primitive, such as |
| // Fence, as a way for one or more observers to poll or wait for notification. |
| // |
| // External synchronization primitives can be wrapped in WaitHandles to enable |
| // other libraries or languages to be waited on alongside WaitHandles created |
| // by the IREE primitives like Fence. See the notes on WaitHandleType for a list |
| // of handle types that are supported. |
| // |
| // Wait handles are thread-safe in that multiple threads may be waiting on them |
| // concurrently. |
| class WaitHandle { |
| public: |
| // Returns a WaitHandle that when waited on will never block. |
| static WaitHandle AlwaysSignaling(); |
| |
| // Returns a WaitHandle that when waited on will always fail. |
| static WaitHandle AlwaysFailing(); |
| |
| using WaitHandleSpan = absl::Span<WaitHandle* const>; |
| |
| // Blocks the caller until all passed |wait_handles| are signaled or the |
| // |deadline| elapses. |
| // |
| // Returns success if the wait is successful and all events have been |
| // signaled. |
| // |
| // Returns DEADLINE_EXCEEDED if the |deadline| elapses without all handles |
| // having been signaled. Note that a subset of the |wait_handles| may have |
| // been signaled and each can be queried to see which one. |
| static Status WaitAll(WaitHandleSpan wait_handles, absl::Time deadline); |
| static Status WaitAll(WaitHandleSpan wait_handles, absl::Duration timeout) { |
| return WaitAll(wait_handles, RelativeTimeoutToDeadline(timeout)); |
| } |
| static Status WaitAll(WaitHandleSpan wait_handles) { |
| return WaitAll(wait_handles, absl::InfiniteFuture()); |
| } |
| |
| // Tries waiting on the handles and returns immediately if it would have |
| // blocked. The caller will not be blocked even if a handle has not yet been |
| // signaled. |
| // |
| // Returns true if all handles have been signaled. |
| static StatusOr<bool> TryWaitAll(WaitHandleSpan wait_handles); |
| |
| // Blocks the caller until at least one of the |wait_handles| is signaled or |
| // the |deadline| elapses. |
| // |
| // Returns the index into |wait_handles| of a handle that was signaled. Note |
| // that more than one handle may have been signaled and all of the other |
| // |wait_handles| should be queried or waited on again until waits for them |
| // succeed. |
| // |
| // Returns DEADLINE_EXCEEDED if the |deadline| elapses without any handles |
| // having been signaled. |
| static StatusOr<int> WaitAny(WaitHandleSpan wait_handles, |
| absl::Time deadline); |
| static StatusOr<int> WaitAny(WaitHandleSpan wait_handles, |
| absl::Duration timeout) { |
| return WaitAny(wait_handles, RelativeTimeoutToDeadline(timeout)); |
| } |
| static StatusOr<int> WaitAny(WaitHandleSpan wait_handles) { |
| return WaitAny(wait_handles, absl::InfiniteFuture()); |
| } |
| |
| // Tries waiting for at least one handle to complete and returns immediately |
| // if none have been. The caller will not be blocked even if a handle has not |
| // yet been signaled. |
| // |
| // Returns the index into |wait_handles| of a handle that was signaled. Note |
| // that more than one handle may have been signaled and all of the other |
| // |wait_handles| should be queried or waited on again until waits for them |
| // succeed. |
| // |
| // Returns -1 if no handles were signaled. |
| static StatusOr<int> TryWaitAny(WaitHandleSpan wait_handles); |
| |
| // Default constructor creates a permanently signaled handle. |
| // Waiting on this handle will never block. |
| WaitHandle() = default; |
| |
| // Wraps an existing sync file descriptor. |
| // Ownership of the file descriptor is transferred to the WaitHandle and must |
| // be duplicated by the caller if they want to continue using it. |
| explicit WaitHandle(ref_ptr<WaitableObject> object); |
| |
| ~WaitHandle(); |
| |
| // Copying not supported. Create a new WaitHandle from the source. |
| WaitHandle(const WaitHandle&) = delete; |
| WaitHandle& operator=(const WaitHandle&) = delete; |
| |
| // Moving supported; sync primitive ownership is transferred. |
| WaitHandle(WaitHandle&& other); |
| WaitHandle& operator=(WaitHandle&& other); |
| |
| // Unique ID for the WaitHandle instance. |
| // Two wait handles, even if waiting on the same underlying primitive, will |
| // have differing unique_ids. This can be used for deduping the handles or |
| // storing handles in a map. |
| uint64_t unique_id() const { return unique_id_; } |
| |
| // Returns a unique string representing the handle. |
| std::string DebugString() const; |
| |
| // Blocks the caller until the handle is signaled or the |deadline| elapses. |
| // |
| // If waiting on multiple wait handles use WaitAll or WaitAny instead of |
| // multiple calls to Wait as they can significantly reduce overhead. |
| // |
| // Returns success if the wait is successful and the |wait_handle| was |
| // signaled. Returns DEADLINE_EXCEEDED if the timeout elapses without the |
| // handle having been signaled. |
| Status Wait(absl::Time deadline) { return WaitAll({this}, deadline); } |
| Status Wait(absl::Duration timeout) { |
| return WaitAll({this}, RelativeTimeoutToDeadline(timeout)); |
| } |
| Status Wait() { return WaitAll({this}, absl::InfiniteFuture()); } |
| |
| // Tries waiting on the handle and returns immediately if it would have |
| // waited. The caller will not be blocked even if the handle has not yet been |
| // signaled. |
| // |
| // Returns true if the handle has been signaled. |
| StatusOr<bool> TryWait(); |
| |
| // These accessors should generally be considered opaque but may be useful to |
| // code trying to interop with other runtimes. |
| const ref_ptr<WaitableObject>& object() const { return object_; } |
| |
| private: |
| // Disposes the handle by closing the fd and issuing callbacks. |
| void Dispose(); |
| |
| static std::atomic<uint64_t> next_unique_id_; |
| |
| uint64_t unique_id_ = 0; |
| ref_ptr<WaitableObject> object_; |
| }; |
| |
| // A manually-resettable event primitive. |
| // Effectively a binary semaphore with a maximum_count of 1 when running in |
| // auto-reset mode but also provides a sticky manual reset mode. |
| class ManualResetEvent : public WaitableObject { |
| public: |
| explicit ManualResetEvent(const char* debug_name = nullptr); |
| |
| ~ManualResetEvent() override; |
| |
| // Copying not supported. |
| ManualResetEvent(const ManualResetEvent&) = delete; |
| ManualResetEvent& operator=(const ManualResetEvent&) = delete; |
| |
| // Moving supported; sync primitive ownership is transferred. |
| ManualResetEvent(ManualResetEvent&& other); |
| ManualResetEvent& operator=(ManualResetEvent&& other); |
| |
| std::string DebugString() const override; |
| |
| // Sets the specified event object to the signaled state. |
| // The event stays signaled until Reset is called. Multiple waiters will be |
| // woken. |
| Status Set(); |
| |
| // Resets the specified event object to the nonsignaled state. |
| // Resetting an event that is already reset has no effect. |
| Status Reset(); |
| |
| // Returns a WaitHandle that will be signaled when the event is set. |
| WaitHandle OnSet(); |
| |
| protected: |
| void Initialize(); |
| void Dispose(); |
| |
| StatusOr<std::pair<FdType, int>> AcquireFdForWait( |
| absl::Time deadline) override { |
| return std::make_pair(fd_type_, fd_); |
| } |
| StatusOr<bool> TryResolveWakeOnFd(int fd) override { return true; } |
| |
| FdType fd_type_ = FdType::kPermanent; |
| int fd_ = kInvalidFd; |
| int write_fd_ = kInvalidFd; // Used only for fd_type_ == kPipe. |
| const char* debug_name_ = nullptr; |
| }; |
| |
| } // namespace iree |
| |
| #endif // IREE_BASE_WAIT_HANDLE_H_ |