master
cpp 296 lines 8.26 KB
Raw
1 // Copyright (C) Microsoft Corporation. All rights reserved.
2 #include "precomp.h"
3 #include "p9io.h"
4
5 #ifndef TEMP_FAILURE_RETRY
6 #define TEMP_FAILURE_RETRY(expression) \
7 (__extension__({ \
8 long int __result; \
9 do \
10 __result = (long int)(expression); \
11 while (__result == -1L && errno == EINTR); \
12 __result; \
13 }))
14 #endif
15
16 namespace p9fs {
17
18 EpollWatcher g_Watcher;
19
20 CoroutineIoIssuer::CoroutineIoIssuer(int fd) : m_FileDescriptor(fd)
21 {
22 }
23
24 void CoroutineIoIssuer::Callback(sigval value)
25 {
26 const auto operation = static_cast<CoroutineIoOperation*>(value.sival_ptr);
27 auto error = aio_error(&operation->ControlBlock);
28 if (error == EINPROGRESS)
29 {
30 return;
31 }
32 auto bytesTransferred = aio_return(&operation->ControlBlock);
33
34 operation->Result = {-error, error == 0 ? static_cast<size_t>(bytesTransferred) : 0};
35 if (!operation->DoneOrCoroutine.exchange(true))
36 {
37 return;
38 }
39
40 // TODO: Can we use this thread to resume the coroutine?
41 g_Scheduler.Schedule(operation->Coroutine);
42 }
43
44 bool CoroutineIoIssuer::PreIssue(CoroutineIoOperation& operation, CancelToken& token)
45 {
46 // Register the IO for cancellation.
47 operation.ControlBlock = {};
48 operation.ControlBlock.aio_fildes = m_FileDescriptor;
49 operation.ControlBlock.aio_sigevent.sigev_notify = SIGEV_THREAD;
50 operation.ControlBlock.aio_sigevent.sigev_notify_function = Callback;
51 operation.ControlBlock.aio_sigevent.sigev_value.sival_ptr = &operation;
52 if (token.Register(operation))
53 {
54 return true;
55 }
56
57 // The operation has already been cancelled. Don't even issue the IO.
58 operation.Result = {-ECANCELED, 0};
59 operation.DoneOrCoroutine = true;
60 return false;
61 }
62
63 void CoroutineIoIssuer::IssueFailed(CancelToken& token)
64 {
65 // Unwind the work done in PreIssue.
66 token.Unregister();
67 }
68
69 void CoroutineIoIssuer::PostIssue(CoroutineIoOperation& operation, CancelToken& token, IoResult result)
70 {
71 if (result.Error != 0)
72 {
73 // The IO completed synchronously.
74 operation.Result = result;
75 operation.DoneOrCoroutine = true;
76
77 WI_ASSERT(operation.Coroutine == nullptr);
78 }
79 else if (token.Cancelled())
80 {
81 // The IO did not complete synchronously, but the operation has been
82 // cancelled. Depending on when the cancel occurred, the IO may not have
83 // been cancelled, so cancel it now.
84 aio_cancel(operation.ControlBlock.aio_fildes, &operation.ControlBlock);
85 }
86 }
87
88 // Register an epoll operation for either an in or an out event.
89 // Returns true if the operation must suspend to wait for the event, or false if the operation can
90 // resume immediately.
91 // N.B. There can be only one operation registered at a time for each event.
92 bool EpollDispatcher::Register(int event, CoroutineEpollOperation& operation)
93 {
94 std::scoped_lock<std::mutex> lock{m_lock};
95 if (event == EPOLLIN)
96 {
97 FAIL_FAST_IF(m_inOperation != nullptr);
98 if (WI_IsFlagSet(m_currentEvents, EPOLLIN))
99 {
100 WI_ClearFlag(m_currentEvents, EPOLLIN);
101
102 // Since Register is called before the operation is registered for cancellation,
103 // it's not possible for this to return false.
104 operation.SetResult(0);
105 return false;
106 }
107
108 m_inOperation = &operation;
109 }
110 else
111 {
112 FAIL_FAST_IF(event != EPOLLOUT);
113 FAIL_FAST_IF(m_outOperation != nullptr);
114 if (WI_IsFlagSet(m_currentEvents, EPOLLOUT))
115 {
116 WI_ClearFlag(m_currentEvents, EPOLLOUT);
117 operation.SetResult(0);
118 return false;
119 }
120
121 m_outOperation = &operation;
122 }
123
124 return true;
125 }
126
127 // Removes the handler for the specified event (EPOLLIN or EPOLLOUT).
128 void EpollDispatcher::Remove(int event)
129 {
130 std::scoped_lock<std::mutex> lock{m_lock};
131 if (event == EPOLLIN)
132 {
133 m_inOperation = nullptr;
134 }
135 else
136 {
137 FAIL_FAST_IF(event != EPOLLOUT);
138 m_outOperation = nullptr;
139 }
140 }
141
142 // Notifies the dispatcher an event has occurred.
143 void EpollDispatcher::Notify(int events)
144 {
145 std::scoped_lock<std::mutex> lock{m_lock};
146
147 // Resume the out operation first, since that is responding to an existing message rather than
148 // reading the request for a new one.
149 if (WI_IsFlagSet(events, EPOLLOUT))
150 {
151 if (m_outOperation != nullptr)
152 {
153 m_outOperation->Resume(0);
154 m_outOperation = nullptr;
155 }
156 else
157 {
158 // If no operation is registered, remember the event occurred for the next time one
159 // is registered.
160 WI_SetFlag(m_currentEvents, EPOLLOUT);
161 }
162 }
163
164 if (WI_IsFlagSet(events, EPOLLIN))
165 {
166 if (m_inOperation != nullptr)
167 {
168 m_inOperation->Resume(0);
169 m_inOperation = nullptr;
170 }
171 else
172 {
173 // If no operation is registered, remember the event occurred for the next time one
174 // is registered.
175 WI_SetFlag(m_currentEvents, EPOLLIN);
176 }
177 }
178 }
179
180 void EpollWatcher::Run()
181 {
182 FAIL_FAST_IF(m_EpollFileDescriptor >= 0);
183
184 m_EpollFileDescriptor = epoll_create1(EPOLL_CLOEXEC);
185 THROW_LAST_ERROR_IF(m_EpollFileDescriptor < 0);
186
187 std::thread(WatchThread, this).detach();
188 }
189
190 void EpollWatcher::Add(int fd, int events, EpollDispatcher& dispatcher)
191 {
192 epoll_event event{};
193 event.events = events;
194 event.data.ptr = &dispatcher;
195 THROW_LAST_ERROR_IF(epoll_ctl(m_EpollFileDescriptor, EPOLL_CTL_ADD, fd, &event) < 0);
196 }
197
198 void EpollWatcher::Remove(int fd)
199 {
200 THROW_LAST_ERROR_IF(epoll_ctl(m_EpollFileDescriptor, EPOLL_CTL_DEL, fd, nullptr) < 0);
201 }
202
203 void EpollWatcher::WatchThread(EpollWatcher* watcher)
204 {
205 for (;;)
206 {
207 epoll_event events[10];
208 int result = TEMP_FAILURE_RETRY(epoll_wait(watcher->m_EpollFileDescriptor, events, 10, -1));
209 THROW_LAST_ERROR_IF(result < 0);
210
211 for (int i = 0; i < result; ++i)
212 {
213 if (events[i].data.ptr != nullptr)
214 {
215 const auto dispatcher = static_cast<EpollDispatcher*>(events[i].data.ptr);
216 dispatcher->Notify(events[i].events);
217 }
218 }
219 }
220 }
221
222 Task<size_t> RecvAsync(CoroutineEpollIssuer& socket, gsl::span<gsl::byte> buffer, CancelToken& token)
223 {
224 CoroutineEpollOperation operation;
225 auto result =
226 co_await socket.Issue<ssize_t>(operation, token, EPOLLIN, [&](int fd) { return recv(fd, buffer.data(), buffer.size(), 0); });
227
228 if (result < 0)
229 {
230 THROW_ERRNO(-result);
231 }
232
233 co_return static_cast<size_t>(result);
234 }
235
236 Task<size_t> SendAsync(CoroutineEpollIssuer& socket, gsl::span<const gsl::byte> buffer, CancelToken& token)
237 {
238 CoroutineEpollOperation operation;
239 auto result = co_await socket.Issue<ssize_t>(
240 operation, token, EPOLLOUT, [&](int fd) { return send(fd, buffer.data(), buffer.size(), 0); });
241
242 if (result < 0)
243 {
244 THROW_ERRNO(-result);
245 }
246
247 co_return static_cast<size_t>(result);
248 }
249
250 Task<int> AcceptAsync(CoroutineEpollIssuer& listen, CancelToken& token)
251 {
252 CoroutineEpollOperation operation;
253 auto result = co_await listen.Issue<int>(
254 operation, token, EPOLLIN, [&](int fd) { return accept4(fd, nullptr, nullptr, SOCK_NONBLOCK | SOCK_CLOEXEC); });
255
256 if (result < 0)
257 {
258 THROW_ERRNO(-result);
259 }
260
261 co_return result;
262 }
263
264 Task<IoResult> ReadAsync(CoroutineIoIssuer& file, std::uint64_t offset, gsl::span<gsl::byte> buffer, CancelToken& token)
265 {
266 CoroutineIoOperation operation;
267 co_return co_await file.Issue(operation, token, [&](aiocb& cb) -> IoResult {
268 cb.aio_buf = buffer.data();
269 cb.aio_nbytes = buffer.size();
270 cb.aio_offset = offset;
271 if (aio_read(&cb) < 0)
272 {
273 return {-errno, 0};
274 }
275
276 return {};
277 });
278 }
279
280 Task<IoResult> WriteAsync(CoroutineIoIssuer& file, std::uint64_t offset, gsl::span<const gsl::byte> buffer, CancelToken& token)
281 {
282 CoroutineIoOperation operation;
283 co_return co_await file.Issue(operation, token, [&](aiocb& cb) -> IoResult {
284 cb.aio_buf = (volatile void*)buffer.data();
285 cb.aio_nbytes = buffer.size();
286 cb.aio_offset = offset;
287 if (aio_write(&cb) < 0)
288 {
289 return {-errno, 0};
290 }
291
292 return {};
293 });
294 }
295
296 } // namespace p9fs