Line data Source code
1 : #include "source/common/event/file_event_impl.h" 2 : 3 : #include <cstdint> 4 : 5 : #include "source/common/common/assert.h" 6 : #include "source/common/event/dispatcher_impl.h" 7 : 8 : #include "event2/event.h" 9 : 10 : namespace Envoy { 11 : namespace Event { 12 : 13 : FileEventImpl::FileEventImpl(DispatcherImpl& dispatcher, os_fd_t fd, FileReadyCb cb, 14 : FileTriggerType trigger, uint32_t events) 15 : : dispatcher_(dispatcher), cb_(cb), fd_(fd), trigger_(trigger), enabled_events_(events), 16 2891 : activation_cb_(dispatcher.createSchedulableCallback([this]() { 17 2891 : ASSERT(injected_activation_events_ != 0); 18 2891 : mergeInjectedEventsAndRunCb(0); 19 4306 : })) { 20 : // Treat the lack of a valid fd (which in practice should only happen if we run out of FDs) as 21 : // an OOM condition and just crash. 22 3411 : RELEASE_ASSERT(SOCKET_VALID(fd), ""); 23 : #ifdef WIN32 24 : ASSERT(trigger_ != FileTriggerType::Edge, "libevent does not support edge triggers on Windows"); 25 : #endif 26 3411 : if constexpr (PlatformDefaultTriggerType != FileTriggerType::EmulatedEdge) { 27 3411 : ASSERT(trigger_ != FileTriggerType::EmulatedEdge, 28 3411 : "Cannot use EmulatedEdge events if they are not the default platform type"); 29 3411 : } 30 : 31 3411 : assignEvents(events, &dispatcher.base()); 32 3411 : event_add(&raw_event_, nullptr); 33 3411 : } 34 : 35 7016 : void FileEventImpl::activate(uint32_t events) { 36 7016 : ASSERT(dispatcher_.isThreadSafe()); 37 : 38 : // events is not empty. 39 7016 : ASSERT(events != 0); 40 : // Only supported event types are set. 41 7016 : ASSERT((events & (FileReadyType::Read | FileReadyType::Write | FileReadyType::Closed)) == events); 42 : 43 : // Schedule the activation callback so it runs as part of the next loop iteration if it is not 44 : // already scheduled. 45 7016 : if (injected_activation_events_ == 0) { 46 4194 : ASSERT(!activation_cb_->enabled()); 47 4194 : activation_cb_->scheduleCallbackNextIteration(); 48 4194 : } 49 7016 : ASSERT(activation_cb_->enabled()); 50 : 51 : // Merge new events with pending injected events. 52 7016 : injected_activation_events_ |= events; 53 7016 : } 54 : 55 4081 : void FileEventImpl::assignEvents(uint32_t events, event_base* base) { 56 4081 : ASSERT(dispatcher_.isThreadSafe()); 57 4081 : ASSERT(base != nullptr); 58 : 59 4081 : enabled_events_ = events; 60 4081 : event_assign( 61 4081 : &raw_event_, base, fd_, 62 4081 : EV_PERSIST | (trigger_ == FileTriggerType::Edge ? EV_ET : 0) | 63 4081 : (events & FileReadyType::Read ? EV_READ : 0) | 64 4081 : (events & FileReadyType::Write ? EV_WRITE : 0) | 65 4081 : (events & FileReadyType::Closed ? EV_CLOSED : 0), 66 5729 : [](evutil_socket_t, short what, void* arg) -> void { 67 5722 : auto* event = static_cast<FileEventImpl*>(arg); 68 5722 : uint32_t events = 0; 69 5722 : if (what & EV_READ) { 70 4088 : events |= FileReadyType::Read; 71 4088 : } 72 : 73 5722 : if (what & EV_WRITE) { 74 4119 : events |= FileReadyType::Write; 75 4119 : } 76 : 77 5722 : if (what & EV_CLOSED) { 78 341 : events |= FileReadyType::Closed; 79 341 : } 80 : 81 5722 : ASSERT(events != 0); 82 5722 : event->mergeInjectedEventsAndRunCb(events); 83 5722 : }, 84 4081 : this); 85 4081 : } 86 : 87 670 : void FileEventImpl::updateEvents(uint32_t events) { 88 670 : ASSERT(dispatcher_.isThreadSafe()); 89 : // The update can be skipped in cases where the old and new event mask are the same if the fd is 90 : // using Level or EmulatedEdge trigger modes, but not Edge trigger mode. When the fd is registered 91 : // in edge trigger mode, re-registering the fd will force re-computation of the readable/writable 92 : // state even in cases where the event mask is not changing. See 93 : // https://github.com/envoyproxy/envoy/pull/16389 for more details. 94 : // TODO(antoniovicente) Consider ways to optimize away event registration updates in edge trigger 95 : // mode once setEnabled stops clearing injected_activation_events_ before calling updateEvents 96 : // and/or implement optimizations at the Network::ConnectionImpl level to reduce the number of 97 : // calls to setEnabled. 98 670 : if (events == enabled_events_ && trigger_ != FileTriggerType::Edge) { 99 0 : return; 100 0 : } 101 670 : auto* base = event_get_base(&raw_event_); 102 670 : event_del(&raw_event_); 103 670 : assignEvents(events, base); 104 670 : event_add(&raw_event_, nullptr); 105 670 : } 106 : 107 670 : void FileEventImpl::setEnabled(uint32_t events) { 108 670 : ASSERT(dispatcher_.isThreadSafe()); 109 670 : if (injected_activation_events_ != 0) { 110 : // Clear pending events on updates to the fd event mask to avoid delivering events that are no 111 : // longer relevant. Updating the event mask will reset the fd edge trigger state so the proxy 112 : // will be able to determine the fd read/write state without need for the injected activation 113 : // events. 114 302 : injected_activation_events_ = 0; 115 302 : activation_cb_->cancel(); 116 302 : } 117 670 : updateEvents(events); 118 670 : } 119 : 120 0 : void FileEventImpl::unregisterEventIfEmulatedEdge(uint32_t event) { 121 0 : ASSERT(dispatcher_.isThreadSafe()); 122 : // This constexpr if allows the compiler to optimize away the function on POSIX 123 0 : if constexpr (PlatformDefaultTriggerType == FileTriggerType::EmulatedEdge) { 124 0 : if (trigger_ == FileTriggerType::EmulatedEdge) { 125 0 : auto new_event_mask = enabled_events_ & ~event; 126 0 : updateEvents(new_event_mask); 127 0 : } 128 0 : } 129 0 : } 130 : 131 0 : void FileEventImpl::registerEventIfEmulatedEdge(uint32_t event) { 132 0 : ASSERT(dispatcher_.isThreadSafe()); 133 : // This constexpr if allows the compiler to optimize away the function on POSIX 134 0 : if constexpr (PlatformDefaultTriggerType == FileTriggerType::EmulatedEdge) { 135 0 : ASSERT((event & (FileReadyType::Read | FileReadyType::Write)) == event); 136 0 : if (trigger_ == FileTriggerType::EmulatedEdge) { 137 0 : auto new_event_mask = enabled_events_ | event; 138 0 : updateEvents(new_event_mask); 139 0 : } 140 0 : } 141 0 : } 142 : 143 8614 : void FileEventImpl::mergeInjectedEventsAndRunCb(uint32_t events) { 144 8614 : ASSERT(dispatcher_.isThreadSafe()); 145 8614 : if (injected_activation_events_ != 0) { 146 3529 : events |= injected_activation_events_; 147 3529 : injected_activation_events_ = 0; 148 3529 : activation_cb_->cancel(); 149 3529 : } 150 : 151 : // TODO(davinci26): This can be optimized further in (w)epoll backends using the `EPOLLONESHOT` 152 : // flag. With this flag `EPOLLIN`/`EPOLLOUT` are automatically disabled when the event is 153 : // activated. 154 8614 : if constexpr (PlatformDefaultTriggerType == FileTriggerType::EmulatedEdge) { 155 0 : if (trigger_ == FileTriggerType::EmulatedEdge) { 156 0 : unregisterEventIfEmulatedEdge(events & 157 0 : (Event::FileReadyType::Write | Event::FileReadyType::Read)); 158 0 : } 159 0 : } 160 : 161 8614 : cb_(events); 162 8614 : } 163 : 164 : } // namespace Event 165 : } // namespace Envoy