pitrou commented on code in PR #37040:
URL: https://github.com/apache/arrow/pull/37040#discussion_r1295967434


##########
cpp/src/arrow/device.h:
##########
@@ -98,6 +101,71 @@ class ARROW_EXPORT Device : public 
std::enable_shared_from_this<Device>,
   /// \brief Return the DeviceAllocationType of this device
   virtual DeviceAllocationType device_type() const = 0;
 
+  /// \brief EXPERIMENTAL: An object that provides event/stream sync primitives
+  class ARROW_EXPORT SyncEvent {
+   public:
+    virtual ~SyncEvent() = default;
+
+    /// @brief Block until sync event is completed.
+    virtual Status wait() = 0;
+
+    /// @brief Make the provided stream wait on the sync event.
+    ///
+    /// Tells the provided stream that it should wait until the
+    /// synchronization event is completed without blocking the CPU.
+    /// @param stream Should be appropriate for the underlying device

Review Comment:
   Does this need to be exposed at the base class level if it takes a 
device-specific param?



##########
cpp/src/arrow/device.h:
##########
@@ -19,9 +19,12 @@
 
 #include <cstdint>
 #include <memory>
+#include <mutex>

Review Comment:
   Is this unused?



##########
cpp/src/arrow/device.h:
##########
@@ -98,6 +101,71 @@ class ARROW_EXPORT Device : public 
std::enable_shared_from_this<Device>,
   /// \brief Return the DeviceAllocationType of this device
   virtual DeviceAllocationType device_type() const = 0;
 
+  /// \brief EXPERIMENTAL: An object that provides event/stream sync primitives
+  class ARROW_EXPORT SyncEvent {
+   public:
+    virtual ~SyncEvent() = default;
+
+    /// @brief Block until sync event is completed.
+    virtual Status wait() = 0;

Review Comment:
   Nit: non-accessor methods should be CamelCase.



##########
cpp/src/arrow/device.h:
##########
@@ -98,6 +101,71 @@ class ARROW_EXPORT Device : public 
std::enable_shared_from_this<Device>,
   /// \brief Return the DeviceAllocationType of this device
   virtual DeviceAllocationType device_type() const = 0;
 
+  /// \brief EXPERIMENTAL: An object that provides event/stream sync primitives
+  class ARROW_EXPORT SyncEvent {
+   public:
+    virtual ~SyncEvent() = default;
+
+    /// @brief Block until sync event is completed.
+    virtual Status wait() = 0;
+
+    /// @brief Make the provided stream wait on the sync event.
+    ///
+    /// Tells the provided stream that it should wait until the
+    /// synchronization event is completed without blocking the CPU.
+    /// @param stream Should be appropriate for the underlying device
+    virtual Status stream_wait(void* stream) = 0;
+
+    void set_stream(void* stream) {
+      stream_ = stream;
+    }
+
+    /// @brief Returns the stored raw event or creates a new one to return.

Review Comment:
   I don't understand the motivation for this API. It also makes the class 
thread-unsafe, which, while manageable, is a bit unfortunate for a 
synchronization API :-)
   
   I would expect `SyncEvent` to be initialized with the right event and not 
attempt to create one dynamically afterwards.



##########
cpp/src/arrow/device.h:
##########
@@ -98,6 +101,71 @@ class ARROW_EXPORT Device : public 
std::enable_shared_from_this<Device>,
   /// \brief Return the DeviceAllocationType of this device
   virtual DeviceAllocationType device_type() const = 0;
 
+  /// \brief EXPERIMENTAL: An object that provides event/stream sync primitives
+  class ARROW_EXPORT SyncEvent {
+   public:
+    virtual ~SyncEvent() = default;
+
+    /// @brief Block until sync event is completed.
+    virtual Status wait() = 0;
+
+    /// @brief Make the provided stream wait on the sync event.
+    ///
+    /// Tells the provided stream that it should wait until the
+    /// synchronization event is completed without blocking the CPU.
+    /// @param stream Should be appropriate for the underlying device
+    virtual Status stream_wait(void* stream) = 0;
+
+    void set_stream(void* stream) {
+      stream_ = stream;
+    }
+
+    /// @brief Returns the stored raw event or creates a new one to return.
+    ///
+    /// clear_event should always be called to cleanup afterwards. If this
+    /// creates the event, then clear_event will call release_event
+    /// internally. If this doesn't own the event, then the lifetime should
+    /// be controlled externally to this class.
+    Result<void*> get_event() {
+      if (!sync_event_) {
+        ARROW_ASSIGN_OR_RAISE(sync_event_, create_event());
+        owns_event_ = true;
+      }
+      return sync_event_;
+    }
+
+    void clear_event() {
+      if (owns_event_) {
+        release_event(sync_event_);
+      }
+      sync_event_ = nullptr;
+    }
+
+    Status record_event() {
+      if (!stream_) {
+        return Status::Invalid(
+            "Cannot record event on null stream, call set_stream first.");
+      }
+      ARROW_ASSIGN_OR_RAISE(auto ev, get_event());
+      return record_event_on_stream(ev);
+    }
+
+   protected:
+    /// If creating this with a passed in event, the caller must ensure
+    /// that the event lives until clear_event is called on this as it
+    /// won't own it.
+    explicit SyncEvent(void* sync_event) : sync_event_{sync_event}, 
owns_event_{false} {}
+
+    virtual Status record_event_on_stream(void* event) = 0;

Review Comment:
   This seems more like a `Device` method to me? We could keep `SyncEvent` a 
simple wrapper around the device-specific raw event.



##########
cpp/src/arrow/device.h:
##########
@@ -98,6 +101,71 @@ class ARROW_EXPORT Device : public 
std::enable_shared_from_this<Device>,
   /// \brief Return the DeviceAllocationType of this device
   virtual DeviceAllocationType device_type() const = 0;
 
+  /// \brief EXPERIMENTAL: An object that provides event/stream sync primitives
+  class ARROW_EXPORT SyncEvent {
+   public:
+    virtual ~SyncEvent() = default;
+
+    /// @brief Block until sync event is completed.
+    virtual Status wait() = 0;
+
+    /// @brief Make the provided stream wait on the sync event.
+    ///
+    /// Tells the provided stream that it should wait until the
+    /// synchronization event is completed without blocking the CPU.
+    /// @param stream Should be appropriate for the underlying device
+    virtual Status stream_wait(void* stream) = 0;
+
+    void set_stream(void* stream) {

Review Comment:
   I'm not sure I understand the point of storing a stream in addition to the 
event. I think limiting this class to a single responsibility makes the API 
more easily understood.



##########
cpp/src/arrow/device.h:
##########
@@ -98,6 +101,71 @@ class ARROW_EXPORT Device : public 
std::enable_shared_from_this<Device>,
   /// \brief Return the DeviceAllocationType of this device
   virtual DeviceAllocationType device_type() const = 0;
 
+  /// \brief EXPERIMENTAL: An object that provides event/stream sync primitives
+  class ARROW_EXPORT SyncEvent {
+   public:
+    virtual ~SyncEvent() = default;
+
+    /// @brief Block until sync event is completed.
+    virtual Status wait() = 0;
+
+    /// @brief Make the provided stream wait on the sync event.
+    ///
+    /// Tells the provided stream that it should wait until the
+    /// synchronization event is completed without blocking the CPU.
+    /// @param stream Should be appropriate for the underlying device
+    virtual Status stream_wait(void* stream) = 0;
+
+    void set_stream(void* stream) {
+      stream_ = stream;
+    }
+
+    /// @brief Returns the stored raw event or creates a new one to return.
+    ///
+    /// clear_event should always be called to cleanup afterwards. If this
+    /// creates the event, then clear_event will call release_event
+    /// internally. If this doesn't own the event, then the lifetime should
+    /// be controlled externally to this class.
+    Result<void*> get_event() {
+      if (!sync_event_) {
+        ARROW_ASSIGN_OR_RAISE(sync_event_, create_event());
+        owns_event_ = true;
+      }
+      return sync_event_;
+    }
+
+    void clear_event() {
+      if (owns_event_) {
+        release_event(sync_event_);
+      }
+      sync_event_ = nullptr;
+    }
+
+    Status record_event() {
+      if (!stream_) {
+        return Status::Invalid(
+            "Cannot record event on null stream, call set_stream first.");
+      }
+      ARROW_ASSIGN_OR_RAISE(auto ev, get_event());
+      return record_event_on_stream(ev);
+    }
+
+   protected:
+    /// If creating this with a passed in event, the caller must ensure
+    /// that the event lives until clear_event is called on this as it
+    /// won't own it.
+    explicit SyncEvent(void* sync_event) : sync_event_{sync_event}, 
owns_event_{false} {}
+
+    virtual Status record_event_on_stream(void* event) = 0;
+    // allowed to gracefully receive a nullptr
+    virtual void release_event(void* event) = 0;

Review Comment:
   Ok, to avoid the annoyance of having to call a virtual function in a 
destructor, I think you can use a unique_ptr with a custom deleter.
   
   It could look like this (untested):
   ```c++
    using OwnedStream = std::unique_ptr<void, void (*)(void *)>;
    OwnedStream stream_;
   ```
   
   and then:
   ```c++
   DeviceSync::OwnedStream WrapCUDAEvent(CUevent event) {
     auto deleter = [](void* event) {
       if (event != nullptr) {
         auto result = cuEventDestroy(reinterpret_cast<CUevent>(event));
         // log error if result isn't successful?
       }
     };
     return OwnedStream(reinterpret_cast<void*>(event), deleter);
   }
   ```
   
   @bkietz Does that look ok?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to