1 /* 2 * Hunt - A refined core library for D programming language. 3 * 4 * Copyright (C) 2018-2019 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.event.selector.Epoll; 13 14 // dfmt off 15 version(HAVE_EPOLL): 16 17 // dfmt on 18 19 import std.exception; 20 import std.socket; 21 import std.string; 22 23 import core.time; 24 import core.stdc.string; 25 import core.stdc.errno; 26 import core.sys.posix.sys.types; 27 import core.sys.posix.netinet.tcp; 28 import core.sys.posix.netinet.in_; 29 import core.sys.posix.unistd; 30 31 import core.sys.posix.sys.resource; 32 import core.sys.posix.sys.time; 33 import core.sys.linux.epoll; 34 35 import hunt.event.selector.Selector; 36 import hunt.Exceptions; 37 import hunt.io.channel; 38 import hunt.logging.ConsoleLogger; 39 import hunt.event.timer; 40 import hunt.system.Error; 41 import hunt.concurrency.TaskPool; 42 43 /* Max. theoretical number of file descriptors on system. */ 44 __gshared size_t fdLimit = 0; 45 46 shared static this() { 47 rlimit fileLimit; 48 getrlimit(RLIMIT_NOFILE, &fileLimit); 49 fdLimit = fileLimit.rlim_max; 50 } 51 52 53 /** 54 */ 55 class AbstractSelector : Selector { 56 enum int NUM_KEVENTS = 1024; 57 private int _epollFD; 58 private bool isDisposed = false; 59 private epoll_event[NUM_KEVENTS] events; 60 private EventChannel _eventChannel; 61 62 this(size_t number, size_t divider, size_t maxChannels = 1500) { 63 super(number, divider, maxChannels); 64 65 // http://man7.org/linux/man-pages/man2/epoll_create.2.html 66 /* 67 * Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor. 68 * See the description of the O_CLOEXEC flag in open(2) for reasons why 69 * this may be useful. 70 */ 71 _epollFD = epoll_create1(EPOLL_CLOEXEC); 72 if (_epollFD < 0) 73 throw new IOException("epoll_create failed"); 74 75 _eventChannel = new EpollEventChannel(this); 76 register(_eventChannel); 77 } 78 79 ~this() @nogc { 80 // dispose(); 81 } 82 83 override void dispose() { 84 if (isDisposed) 85 return; 86 87 version (HUNT_IO_DEBUG) 88 tracef("disposing selector[fd=%d]...", _epollFD); 89 isDisposed = true; 90 _eventChannel.close(); 91 int r = core.sys.posix.unistd.close(_epollFD); 92 if(r != 0) { 93 version (HUNT_IO_DEBUG) warningf("error: %d", r); 94 } 95 96 super.dispose(); 97 } 98 99 override void onStop() { 100 version (HUNT_IO_DEBUG) 101 infof("Selector stopping. fd=%d, id: %d", _epollFD, number); 102 103 if(!_eventChannel.isClosed()) { 104 _eventChannel.trigger(); 105 // _eventChannel.onWrite(); 106 } 107 } 108 109 override bool register(AbstractChannel channel) { 110 assert(channel !is null); 111 int infd = cast(int) channel.handle; 112 version (HUNT_IO_DEBUG) 113 tracef("register channel: fd=%d", infd); 114 115 size_t index = cast(size_t)(infd / divider); 116 if (index >= channels.length) { 117 debug warningf("expanding channels uplimit to %d", index); 118 import std.algorithm : max; 119 120 size_t length = max(cast(size_t)(index * 3 / 2), 16); 121 AbstractChannel[] arr = new AbstractChannel[length]; 122 arr[0 .. channels.length] = channels[0 .. $]; 123 channels = arr; 124 } 125 channels[index] = channel; 126 127 // epoll_event e; 128 129 // e.data.fd = infd; 130 // e.data.ptr = cast(void*) channel; 131 // e.events = EPOLLIN | EPOLLET | EPOLLERR | EPOLLHUP | EPOLLRDHUP | EPOLLOUT; 132 // int s = epoll_ctl(_epollFD, EPOLL_CTL_ADD, infd, &e); 133 // if (s == -1) { 134 // debug warningf("failed to register channel: fd=%d", infd); 135 // return false; 136 // } else { 137 // return true; 138 // } 139 if (epollCtl(channel, EPOLL_CTL_ADD)) { 140 return true; 141 } else { 142 debug warningf("failed to register channel: fd=%d", infd); 143 return false; 144 } 145 } 146 147 override bool deregister(AbstractChannel channel) { 148 size_t fd = cast(size_t) channel.handle; 149 size_t index = cast(size_t)(fd / divider); 150 version (HUNT_IO_DEBUG) 151 tracef("deregister channel: fd=%d, index=%d", fd, index); 152 channels[index] = null; 153 154 if (epollCtl(channel, EPOLL_CTL_DEL)) { 155 return true; 156 } else { 157 warningf("deregister channel failed: fd=%d", fd); 158 return false; 159 } 160 } 161 162 override bool update(AbstractChannel channel) { 163 if (epollCtl(channel, EPOLL_CTL_MOD)) { 164 return true; 165 } else { 166 warningf("update channel failed: fd=%d", fd); 167 return false; 168 } 169 } 170 171 /** 172 timeout: in millisecond 173 */ 174 protected override int doSelect(long timeout) { 175 int len = 0; 176 177 if (timeout <= 0) { /* Indefinite or no wait */ 178 do { 179 // http://man7.org/linux/man-pages/man2/epoll_wait.2.html 180 // https://stackoverflow.com/questions/6870158/epoll-wait-fails-due-to-eintr-how-to-remedy-this/6870391#6870391 181 len = epoll_wait(_epollFD, events.ptr, events.length, cast(int) timeout); 182 } while ((len == -1) && (errno == EINTR)); 183 } else { /* Bounded wait; bounded restarts */ 184 len = iepoll(_epollFD, events.ptr, events.length, cast(int) timeout); 185 } 186 187 if (len > 0) { 188 if(defaultPoolThreads > 0) { // using worker thread 189 foreach (i; 0 .. len) { 190 AbstractChannel channel = cast(AbstractChannel)(events[i].data.ptr); 191 if (channel is null) { 192 debug warningf("channel is null"); 193 } else { 194 uint currentEvents = events[i].events; 195 workerPool.put(cast(int)channel.handle, makeTask(&handeChannelEvent, channel, currentEvents)); 196 } 197 } 198 } else { 199 foreach (i; 0 .. len) { 200 AbstractChannel channel = cast(AbstractChannel)(events[i].data.ptr); 201 if (channel is null) { 202 debug warningf("channel is null"); 203 } else { 204 handeChannelEvent(channel, events[i].events); 205 } 206 } 207 } 208 } 209 210 return len; 211 } 212 213 private void handeChannelEvent(AbstractChannel channel, uint event) { 214 version (HUNT_IO_DEBUG) 215 infof("handling event: selector=%d, events=%d, channel=%d", this._epollFD, event, channel.handle); 216 217 try { 218 if (isClosed(event)) { // && errno != EINTR 219 /* An error has occured on this fd, or the socket is not 220 ready for reading (why were we notified then?) */ 221 version (HUNT_IO_DEBUG) { 222 if (isError(event)) { 223 warningf("channel error: fd=%s, event=%d, errno=%d, message=%s", 224 channel.handle, event, errno, getErrorMessage(errno)); 225 } else { 226 tracef("channel closed: fd=%d, errno=%d, message=%s", 227 channel.handle, errno, getErrorMessage(errno)); 228 } 229 } 230 // FIXME: Needing refactor or cleanup -@zxp at 2/28/2019, 3:25:24 PM 231 // May be triggered twice for a channel, for example: 232 // events=8197, fd=13 233 // events=8221, fd=13 234 // The remote connection broken abnormally, so the channel should be notified. 235 // channel.onClose(); // More tests are needed 236 channel.close(); 237 } else if (event == EPOLLIN) { 238 version (HUNT_IO_DEBUG) 239 tracef("channel read event: fd=%d", channel.handle); 240 channel.onRead(); 241 } else if (event == EPOLLOUT) { 242 version (HUNT_IO_DEBUG) 243 tracef("channel write event: fd=%d", channel.handle); 244 channel.onWrite(); 245 } else if (event == (EPOLLIN | EPOLLOUT)) { 246 version (HUNT_IO_DEBUG) 247 tracef("channel read and write: fd=%d", channel.handle); 248 channel.onWrite(); 249 channel.onRead(); 250 } else { 251 debug warningf("this thread only for read/write/close events, event: %d", event); 252 } 253 } catch (Exception e) { 254 debug { 255 errorf("error while handing channel: fd=%s, exception=%s, message=%s", 256 channel.handle, typeid(e), e.msg); 257 } 258 version(HUNT_DEBUG) warning(e); 259 } 260 } 261 262 private int iepoll(int epfd, epoll_event* events, int numfds, int timeout) { 263 long start, now; 264 int remaining = timeout; 265 timeval t; 266 long diff; 267 268 gettimeofday(&t, null); 269 start = t.tv_sec * 1000 + t.tv_usec / 1000; 270 271 for (;;) { 272 int res = epoll_wait(epfd, events, numfds, remaining); 273 if (res < 0 && errno == EINTR) { 274 if (remaining >= 0) { 275 gettimeofday(&t, null); 276 now = t.tv_sec * 1000 + t.tv_usec / 1000; 277 diff = now - start; 278 remaining -= diff; 279 if (diff < 0 || remaining <= 0) { 280 return 0; 281 } 282 start = now; 283 } 284 } else { 285 return res; 286 } 287 } 288 } 289 290 // https://blog.csdn.net/ljx0305/article/details/4065058 291 private static bool isError(uint events) nothrow { 292 return (events & EPOLLERR) != 0; 293 } 294 295 private static bool isClosed(uint e) nothrow { 296 return (e & EPOLLERR) != 0 || (e & EPOLLHUP) != 0 || (e & EPOLLRDHUP) != 0 297 || (!(e & EPOLLIN) && !(e & EPOLLOUT)) != 0; 298 } 299 300 private static bool isReadable(uint events) nothrow { 301 return (events & EPOLLIN) != 0; 302 } 303 304 private static bool isWritable(uint events) nothrow { 305 return (events & EPOLLOUT) != 0; 306 } 307 308 private static buildEpollEvent(AbstractChannel channel, ref epoll_event ev) { 309 ev.data.ptr = cast(void*) channel; 310 // ev.data.fd = channel.handle; 311 ev.events = EPOLLRDHUP | EPOLLERR | EPOLLHUP; 312 if (channel.hasFlag(ChannelFlag.Read)) 313 ev.events |= EPOLLIN; 314 if (channel.hasFlag(ChannelFlag.Write)) 315 ev.events |= EPOLLOUT; 316 // if (channel.hasFlag(ChannelFlag.OneShot)) 317 // ev.events |= EPOLLONESHOT; 318 if (channel.hasFlag(ChannelFlag.ETMode)) 319 ev.events |= EPOLLET; 320 return ev; 321 } 322 323 private bool epollCtl(AbstractChannel channel, int opcode) { 324 assert(channel !is null); 325 const fd = channel.handle; 326 assert(fd >= 0, "The channel.handle is not initialized!"); 327 328 epoll_event ev; 329 buildEpollEvent(channel, ev); 330 int res = 0; 331 332 do { 333 res = epoll_ctl(_epollFD, opcode, fd, &ev); 334 } 335 while ((res == -1) && (errno == EINTR)); 336 337 /* 338 * A channel may be registered with several Selectors. When each Selector 339 * is polled a EPOLL_CTL_DEL op will be inserted into its pending update 340 * list to remove the file descriptor from epoll. The "last" Selector will 341 * close the file descriptor which automatically unregisters it from each 342 * epoll descriptor. To avoid costly synchronization between Selectors we 343 * allow pending updates to be processed, ignoring errors. The errors are 344 * harmless as the last update for the file descriptor is guaranteed to 345 * be EPOLL_CTL_DEL. 346 */ 347 if (res < 0 && errno != EBADF && errno != ENOENT && errno != EPERM) { 348 warning("epoll_ctl failed"); 349 return false; 350 } else 351 return true; 352 } 353 }