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
161941
    : dispatcher_(dispatcher), cb_(cb), fd_(fd), trigger_(trigger), enabled_events_(events),
16
545696
      activation_cb_(dispatcher.createSchedulableCallback([this]() {
17
524054
        ASSERT(injected_activation_events_ != 0);
18
524054
        mergeInjectedEventsAndRunCb(0);
19
545696
      })) {
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
161941
  RELEASE_ASSERT(SOCKET_VALID(fd), "");
23
#ifdef WIN32
24
  ASSERT(trigger_ != FileTriggerType::Edge, "libevent does not support edge triggers on Windows");
25
#endif
26
161941
  if constexpr (PlatformDefaultTriggerType != FileTriggerType::EmulatedEdge) {
27
161941
    ASSERT(trigger_ != FileTriggerType::EmulatedEdge,
28
161941
           "Cannot use EmulatedEdge events if they are not the default platform type");
29
161941
  }
30

            
31
161941
  assignEvents(events, &dispatcher.base());
32
161941
  event_add(&raw_event_, nullptr);
33
161941
}
34

            
35
1605653
void FileEventImpl::activate(uint32_t events) {
36
1605653
  ASSERT(dispatcher_.isThreadSafe());
37

            
38
  // events is not empty.
39
1605653
  ASSERT(events != 0);
40
  // Only supported event types are set.
41
1605653
  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
1605653
  if (injected_activation_events_ == 0) {
46
628230
    ASSERT(!activation_cb_->enabled());
47
628230
    activation_cb_->scheduleCallbackNextIteration();
48
628230
  }
49
1605653
  ASSERT(activation_cb_->enabled());
50

            
51
  // Merge new events with pending injected events.
52
1605653
  injected_activation_events_ |= events;
53
1605653
}
54

            
55
388146
void FileEventImpl::assignEvents(uint32_t events, event_base* base) {
56
388146
  ASSERT(dispatcher_.isThreadSafe());
57
388146
  ASSERT(base != nullptr);
58

            
59
388146
  enabled_events_ = events;
60
388146
  event_assign(
61
388146
      &raw_event_, base, fd_,
62
388146
      EV_PERSIST | (trigger_ == FileTriggerType::Edge ? EV_ET : 0) |
63
388146
          (events & FileReadyType::Read ? EV_READ : 0) |
64
388146
          (events & FileReadyType::Write ? EV_WRITE : 0) |
65
388146
          (events & FileReadyType::Closed ? EV_CLOSED : 0),
66
981790
      [](evutil_socket_t, short what, void* arg) -> void {
67
954801
        auto* event = static_cast<FileEventImpl*>(arg);
68
954801
        uint32_t events = 0;
69
954801
        if (what & EV_READ) {
70
732906
          events |= FileReadyType::Read;
71
732906
        }
72

            
73
954801
        if (what & EV_WRITE) {
74
894720
          events |= FileReadyType::Write;
75
894720
        }
76

            
77
954801
        if (what & EV_CLOSED) {
78
431
          events |= FileReadyType::Closed;
79
431
        }
80

            
81
954801
        ASSERT(events != 0);
82
954801
        event->mergeInjectedEventsAndRunCb(events);
83
954801
      },
84
388146
      this);
85
388146
}
86

            
87
226304
void FileEventImpl::updateEvents(uint32_t events) {
88
226304
  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
226304
  if (events == enabled_events_ && trigger_ != FileTriggerType::Edge) {
99
99
    return;
100
99
  }
101
226205
  auto* base = event_get_base(&raw_event_);
102
226205
  event_del(&raw_event_);
103
226205
  assignEvents(events, base);
104
226205
  event_add(&raw_event_, nullptr);
105
226205
}
106

            
107
226304
void FileEventImpl::setEnabled(uint32_t events) {
108
226304
  ASSERT(dispatcher_.isThreadSafe());
109
226304
  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
33239
    injected_activation_events_ = 0;
115
33239
    activation_cb_->cancel();
116
33239
  }
117
226304
  updateEvents(events);
118
226304
}
119

            
120
void FileEventImpl::unregisterEventIfEmulatedEdge(uint32_t event) {
121
  ASSERT(dispatcher_.isThreadSafe());
122
  // This constexpr if allows the compiler to optimize away the function on POSIX
123
  if constexpr (PlatformDefaultTriggerType == FileTriggerType::EmulatedEdge) {
124
    if (trigger_ == FileTriggerType::EmulatedEdge) {
125
      auto new_event_mask = enabled_events_ & ~event;
126
      updateEvents(new_event_mask);
127
    }
128
  }
129
}
130

            
131
void FileEventImpl::registerEventIfEmulatedEdge(uint32_t event) {
132
  ASSERT(dispatcher_.isThreadSafe());
133
  // This constexpr if allows the compiler to optimize away the function on POSIX
134
  if constexpr (PlatformDefaultTriggerType == FileTriggerType::EmulatedEdge) {
135
    ASSERT((event & (FileReadyType::Read | FileReadyType::Write)) == event);
136
    if (trigger_ == FileTriggerType::EmulatedEdge) {
137
      auto new_event_mask = enabled_events_ | event;
138
      updateEvents(new_event_mask);
139
    }
140
  }
141
}
142

            
143
1478833
void FileEventImpl::mergeInjectedEventsAndRunCb(uint32_t events) {
144
1478833
  ASSERT(dispatcher_.isThreadSafe());
145
1478833
  if (injected_activation_events_ != 0) {
146
592198
    events |= injected_activation_events_;
147
592198
    injected_activation_events_ = 0;
148
592198
    activation_cb_->cancel();
149
592198
  }
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
  if constexpr (PlatformDefaultTriggerType == FileTriggerType::EmulatedEdge) {
155
    if (trigger_ == FileTriggerType::EmulatedEdge) {
156
      unregisterEventIfEmulatedEdge(events &
157
                                    (Event::FileReadyType::Write | Event::FileReadyType::Read));
158
    }
159
  }
160

            
161
1478833
  THROW_IF_NOT_OK(cb_(events));
162
1478833
}
163

            
164
} // namespace Event
165
} // namespace Envoy