private unsafe void PollThread(object obj) { ThreadContext threadContext = null; Exception error = null; try { // .NET doesn't support setting thread affinity on Start // We could change it before starting the thread // so it gets inherited, but we don't know how many threads // the runtime may start. if (_cpuId != -1) { Scheduler.SetCurrentThreadAffinity(_cpuId); } // objects are allocated on the PollThread heap int pipeKey; threadContext = new ThreadContext(this, _transportOptions, _connectionHandler, CreateLogger()); threadContext.Initialize(); { // register pipe pipeKey = threadContext.PipeEnds.ReadEnd.DangerousGetHandle().ToInt32(); EPollInterop.EPollControl(threadContext.EPollFd, EPollOperation.Add, threadContext.PipeEnds.ReadEnd.DangerousGetHandle().ToInt32(), EPollEvents.Readable, EPollData(pipeKey)); // accept connections AcceptOn(_endPoint, _cpuId, _transportOptions, threadContext); _threadContext = threadContext; } int epollFd = threadContext.EPollFd; var readEnd = threadContext.PipeEnds.ReadEnd; int notPacked = !EPoll.PackedEvents ? 1 : 0; var buffer = stackalloc int[EventBufferLength * (3 + notPacked)]; int statReadEvents = 0; int statWriteEvents = 0; int statAcceptEvents = 0; int statAccepts = 0; var sockets = threadContext.Sockets; var acceptableSockets = new List <TSocket>(1); var readableSockets = new List <TSocket>(EventBufferLength); var writableSockets = new List <TSocket>(EventBufferLength); bool pipeReadable = false; CompleteStateChange(State.Started); bool running = true; do { int numEvents = EPollInterop.EPollWait(epollFd, buffer, EventBufferLength, timeout: EPoll.TimeoutInfinite).Value; // actions can be scheduled without unblocking epoll threadContext.SetEpollNotBlocked(); // check events // we don't handle them immediately: // - this ensures we don't mismatch a closed socket with a new socket that have the same fd // ~ To have the same fd, the previous fd must be closed, which means it is removed from the epoll // ~ and won't show up in our next call to epoll.Wait. // ~ The old fd may be present in the buffer still, but lookup won't give a match, since it is removed // ~ from the dictionary before it is closed. If we were accepting already, a new socket could match. // - this also improves cache/cpu locality of the lookup int *ptr = buffer; lock (sockets) { for (int i = 0; i < numEvents; i++) { // Packed Non-Packed // ------ ------ // 0:Events == Events // 1:Int1 = Key [Padding] // 2:Int2 = Key == Int1 = Key // 3:~~~~~~~~~~ Int2 = Key // ~~~~~~~~~~ int key = ptr[2]; ptr += 3 + notPacked; TSocket tsocket; if (sockets.TryGetValue(key & ~DupKeyMask, out tsocket)) { var type = tsocket.Flags & SocketFlags.TypeMask; if (type == SocketFlags.TypeClient) { bool read = (key & DupKeyMask) == 0; if (read) { readableSockets.Add(tsocket); } else { writableSockets.Add(tsocket); } } else { statAcceptEvents++; acceptableSockets.Add(tsocket); } } else if (key == pipeKey) { pipeReadable = true; } } } // handle accepts statAcceptEvents += acceptableSockets.Count; for (int i = 0; i < acceptableSockets.Count; i++) { statAccepts += HandleAccept(acceptableSockets[i], threadContext); } acceptableSockets.Clear(); // handle writes statWriteEvents += writableSockets.Count; for (int i = 0; i < writableSockets.Count; i++) { writableSockets[i].CompleteWritable(); } writableSockets.Clear(); // handle reads statReadEvents += readableSockets.Count; for (int i = 0; i < readableSockets.Count; i++) { readableSockets[i].CompleteReadable(); } readableSockets.Clear(); // handle pipe if (pipeReadable) { PosixResult result; do { result = readEnd.TryReadByte(); if (result.Value == PipeStopSockets) { StopSockets(threadContext.Sockets); } else if (result.Value == PipeStopThread) { running = false; } } while (result); pipeReadable = false; } // scheduled work threadContext.DoScheduledWork(); } while (running); threadContext.Logger.LogInformation($"Thread {_threadId}: Stats A/AE:{statAccepts}/{statAcceptEvents} RE:{statReadEvents} WE:{statWriteEvents}"); } catch (Exception ex) { error = ex; } finally { // We are not using SafeHandles for epoll to increase performance. // running == false when there are no more Sockets // so we are sure there are no more epoll users. threadContext?.Dispose(); CompleteStateChange(State.Stopped, error); } }