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

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

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

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

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

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

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

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

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

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

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

            
107
226667
void FileEventImpl::setEnabled(uint32_t events) {
108
226667
  ASSERT(dispatcher_.isThreadSafe());
109
226667
  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
33347
    injected_activation_events_ = 0;
115
33347
    activation_cb_->cancel();
116
33347
  }
117
226667
  updateEvents(events);
118
226667
}
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
1471959
void FileEventImpl::mergeInjectedEventsAndRunCb(uint32_t events) {
144
1471959
  ASSERT(dispatcher_.isThreadSafe());
145
1471959
  if (injected_activation_events_ != 0) {
146
607371
    events |= injected_activation_events_;
147
607371
    injected_activation_events_ = 0;
148
607371
    activation_cb_->cancel();
149
607371
  }
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
1471959
  THROW_IF_NOT_OK(cb_(events));
162
1471959
}
163

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