├── .gitignore ├── README.md ├── geventreactor └── __init__.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # geventreactor 2 | 3 | **geventreactor** is a gevent-powered **Twisted** reactor whose goal is to enable mixing of **gevent**- and **Twisted**-oriented code, allowing developers to benefit from the performance of **libevent** and **greenlet** while retaining access to the extensive functionality of **Twisted**. 4 | 5 | 6 | ## Differences From Similar Projects 7 | 8 | - Unlike [corotwine], geventreactor does not provide a special base protocol whose subclasses are utilized in a blocking manner. 9 | - Twisted code looks like Twisted code and gevent code looks like gevent code. As a result, it should not be a challenge for users of either framework to use geventreactor, and standard documentation applies. The integration is so complete, however, that blocking code can be used in most standard Twisted methods. 10 | - Unlike the Twisted hub for [Eventlet], geventreactor makes Twisted run on gevent, not the other way around. 11 | - Eventlet uses a pure-Python reactor loop, so there is not much to be gained from running Twisted on Eventlet. On the other hand, gevent uses the C-based libevent, which is more performant. 12 | - Unlike gTwist (geventreactor's predecessor), geventreactor does not monkey-patch anything, and replaces the reactor in the recommended way. 13 | - The result is cleaner code and fewer quirks. 14 | 15 | [corotwine]: https://launchpad.net/corotwine "corotwine" 16 | [Eventlet]: http://eventlet.net/ "Eventlet" 17 | 18 | 19 | ## Module Contents 20 | 21 | - `GeventReactor` is the gevent-powered reactor. Simply install it before you import `twisted.internet.reactor`: 22 | `import geventreactor; geventreactor.install()` 23 | - `GeventResolver` is the gevent-powered DNS resolver. It is automatically installed in the `GeventReactor`, but can be used by itself à la `ThreadedResolver` 24 | - `GeventThreadPool` manages greenlets in a group but exposes a Twisted-style thread pool interface. It helps `GeventReactor` work seamlessly with the functions in `twisted.internet.threads`. 25 | - `waitForGreenlet` adapts a greenlet to a `Deferred` usable in Twisted methods 26 | - `waitForDeferred` waits until a `Deferred` is fulfilled, blocking for a result or exception 27 | 28 | 29 | ## Example Code 30 | 31 | 32 | ### Client 33 | 34 | ```python 35 | import geventreactor; geventreactor.install() 36 | from twisted.internet import reactor 37 | from twisted.web.client import Agent 38 | from twisted.web.http_headers import Headers 39 | 40 | agent = Agent(reactor) 41 | 42 | d = agent.request( 43 | 'GET', 44 | 'http://example.com/', 45 | Headers({'User-Agent': ['Twisted Web Client Example']}), 46 | None) 47 | 48 | def cbResponse(ignored): 49 | print 'Response received' 50 | d.addCallback(cbResponse) 51 | 52 | def cbShutdown(ignored): 53 | reactor.stop() 54 | d.addBoth(cbShutdown) 55 | 56 | reactor.run() 57 | ``` 58 | 59 | 60 | ### Server 61 | 62 | ```python 63 | import gevent, time 64 | 65 | from twisted.internet.protocol import Factory 66 | from twisted.protocols.basic import LineReceiver 67 | 68 | def greencount(): 69 | s = time.time() 70 | while 1: 71 | gevent.sleep(1) 72 | s0 = time.time() 73 | print s0-s-1 74 | s = s0 75 | 76 | class TwistedFactory(Factory): 77 | class protocol(LineReceiver): 78 | delimiter = '\n' 79 | def connectionMade(self): 80 | print '+connection:',id(self) 81 | self.transport.write(self.factory.quote+'\r\n') 82 | def later(i): 83 | self.transport.write('%d from later()\r\n'%i) 84 | @gevent.Greenlet.spawn 85 | def ninja(): 86 | gevent.sleep(1) 87 | self.transport.write('0 from ninja()\r\n') 88 | gevent.sleep(2) 89 | self.transport.write('2 from ninja()\r\n') 90 | reactor.callLater(1,later,3) 91 | reactor.callLater(2,later,1) 92 | def connectionLost(self,reason): 93 | print '-connection:',id(self),reason 94 | def lineReceived(self,data): 95 | self.stopcounter() 96 | data = data.strip() 97 | ldata = data.lower() 98 | if ldata == 'die': 99 | self.sendLine('stopping reactor') 100 | reactor.stop() 101 | elif ldata == 'quit': 102 | self.sendLine('quitting in 2 seconds') 103 | gevent.sleep(2) 104 | self.transport.loseConnection() 105 | elif ldata == 'delay': 106 | self.sendLine('waiting 10 seconds') 107 | gevent.sleep(10) 108 | self.sendLine('hi again') 109 | elif ldata == 'count': 110 | self.stopcounter() 111 | @gevent.Greenlet.spawn 112 | def count(): 113 | i = 0 114 | while 1: 115 | gevent.sleep(1) 116 | self.sendLine('%d from count()'%i) 117 | i += 1 118 | self.counter = count 119 | else: 120 | self.sendLine(data) 121 | def stopcounter(self): 122 | try: 123 | self.counter.kill() 124 | del self.counter 125 | except AttributeError: 126 | pass 127 | def __init__(self,quote=None): 128 | self.quote = quote or 'An apple a day keeps the doctor away' 129 | 130 | import geventreactor; geventreactor.install() 131 | from twisted.internet import reactor 132 | gevent.Greenlet.spawn(greencount) 133 | reactor.listenTCP(8007,TwistedFactory('Welcome to the geventreactor demo!\r\ncount:\tstart a counter\r\ndelay:\tblock your session for 10 seconds\r\nquit:\tterminate your session after 2 seconds\r\ndie:\tstop the reactor\r\notherwise, simply echo')) 134 | reactor.run() 135 | ``` 136 | 137 | 138 | ## Known Quirks 139 | 140 | Life would be perfect without these, right? 141 | 142 | - You can use blocking code in many places, but not **everywhere**. Each protocol instance has two greenlets dedicated to input and output, so feel free to block in `doRead` (i.e. `dataReceived`, `lineReceived`, ...), and `doRead` won't be called multiple times simultaneously unless you make it do so (i.e. spawn a greenlet and return). Anything that runs in the reactor's greenlet (i.e. `callFromThread`, `callFromGreenlet`, `callLater`, ...) must not block. Deferred callbacks/errbacks should also not block, because they may be called from the reactor's greenlet. Of course, it is okay to spawn new greenlets when blocking is not advisable. So far, I know the following additional methods should not block: 143 | - `Protocol.connectionMade` 144 | - While a reasonable attempt is made for performance, the main concern is compatibility with existing Twisted-oriented code. geventreactor is similar to selectreactor in terms of throughput, but it does not perform as well in the number of connections or Web requests processed per second. Please consider porting performance-critical parts of the project to gevent. Currently, compatibility and performance are tested using jcalderone's [twisted-benchmarks]. 145 | 146 | [twisted-benchmarks]: https://code.launchpad.net/~exarkun/+junk/twisted-benchmarks "twisted-benchmarks" 147 | 148 | 149 | ## License 150 | 151 | ``` 152 | Permission is hereby granted, free of charge, to any person obtaining a copy 153 | of this software and associated documentation files (the "Software"), to deal 154 | in the Software without restriction, including without limitation the rights 155 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 156 | copies of the Software, and to permit persons to whom the Software is 157 | furnished to do so, subject to the following conditions: 158 | 159 | The above copyright notice and this permission notice shall be included in 160 | all copies or substantial portions of the Software. 161 | 162 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 163 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 164 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 165 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 166 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 167 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 168 | THE SOFTWARE. 169 | ``` 170 | -------------------------------------------------------------------------------- /geventreactor/__init__.py: -------------------------------------------------------------------------------- 1 | ## Twisted reactor based on gevent 2 | ## 3 | ## Copyright (C) 2011-2013 by Jiang Yio 4 | ## Copyright (C) 2012 by Matthias Urlichs 5 | ## Copyright (C) 2013 by Erik Allik 6 | ## 7 | ## Permission is hereby granted, free of charge, to any person obtaining a copy 8 | ## of this software and associated documentation files (the "Software"), to deal 9 | ## in the Software without restriction, including without limitation the rights 10 | ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | ## copies of the Software, and to permit persons to whom the Software is 12 | ## furnished to do so, subject to the following conditions: 13 | ## 14 | ## The above copyright notice and this permission notice shall be included in 15 | ## all copies or substantial portions of the Software. 16 | ## 17 | ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | ## THE SOFTWARE. 24 | 25 | 26 | import sys 27 | import traceback 28 | import warnings 29 | from bisect import insort 30 | 31 | import gevent 32 | from gevent import Greenlet, GreenletExit, socket 33 | from gevent.pool import Group 34 | from gevent.event import Event, AsyncResult 35 | 36 | from twisted.python import log, failure, reflect, util 37 | from twisted.python.runtime import seconds as runtimeSeconds 38 | from twisted.internet import defer, error, posixbase 39 | from twisted.internet.base import IDelayedCall, ThreadedResolver 40 | from twisted.internet.threads import deferToThreadPool, deferToThread, callMultipleInThread, blockingCallFromThread 41 | from twisted.persisted import styles 42 | 43 | from zope.interface import Interface, implements 44 | 45 | 46 | __all__ = [ 47 | 'deferToGreenletPool', 48 | 'deferToGreenlet', 49 | 'callMultipleInGreenlet', 50 | 'waitForGreenlet', 51 | 'waitForDeferred', 52 | 'blockingCallFromGreenlet', 53 | 'IReactorGreenlets', 54 | 'GeventThreadPool', 55 | 'GeventResolver', 56 | 'GeventReactor', 57 | 'install' 58 | ] 59 | 60 | 61 | # Common exceptions raised by Stream 62 | _NO_FILENO = error.ConnectionFdescWentAway('Handler has no fileno method') 63 | _NO_FILEDESC = error.ConnectionFdescWentAway('Filedescriptor went away') 64 | 65 | 66 | # Mirrored from twisted.internet.threads for backwards-compatibility 67 | deferToGreenletPool = deferToThreadPool 68 | deferToGreenlet = deferToThread 69 | callMultipleInGreenlet = callMultipleInThread 70 | blockingCallFromGreenlet = blockingCallFromThread 71 | 72 | 73 | def waitForGreenlet(g): 74 | """Link greenlet completion to Deferred""" 75 | d = defer.Deferred() 76 | def cb(g): 77 | try: 78 | d.callback(g.get()) 79 | except: 80 | d.errback(failure.Failure()) 81 | g.link(cb) 82 | return d 83 | 84 | 85 | def waitForDeferred(d,result=None): 86 | """Block current greenlet for Deferred, waiting until result is not a Deferred or a failure is encountered""" 87 | if result is None: 88 | result = AsyncResult() 89 | def cb(res): 90 | if isinstance(res,defer.Deferred): 91 | waitForDeferred(res,result) 92 | else: 93 | result.set(res) 94 | def eb(res): 95 | result.set_exception(res) 96 | d.addCallbacks(cb,eb) 97 | try: 98 | return result.get() 99 | except failure.Failure,ex: 100 | ex.raiseException() 101 | 102 | 103 | class IReactorGreenlets(Interface): 104 | """Interface for reactor supporting greenlets""" 105 | 106 | def getGreenletPool(self): 107 | pass 108 | 109 | def callInGreenlet(self,*args,**kwargs): 110 | pass 111 | 112 | def callFromGreenlet(self,*args,**kw): 113 | pass 114 | 115 | def suggestGreenletPoolSize(self,size): 116 | pass 117 | 118 | 119 | class Reschedule(Exception): 120 | """Event for IReactorTime""" 121 | pass 122 | 123 | 124 | class GeventThreadPool(Group): 125 | """This class allows Twisted to work with a greenlet pool""" 126 | 127 | def __init__(self,*args,**kwargs): 128 | Group.__init__(self,*args,**kwargs) 129 | self.open = True 130 | 131 | def start(self,greenlet=None): 132 | """Start the greenlet pool or add a greenlet to the pool.""" 133 | if greenlet is not None: 134 | return Group.start(self,greenlet) 135 | 136 | def startAWorker(self): 137 | pass 138 | 139 | def stopAWorker(self): 140 | pass 141 | 142 | def callInThread(self,func,*args,**kwargs): 143 | """Call a callable object in a separate greenlet.""" 144 | if self.open: 145 | self.add(Greenlet.spawn_later(0,func,*args,**kwargs)) 146 | 147 | def callInThreadWithCallback(self,onResult,func,*args,**kwargs): 148 | """Call a callable object in a separate greenlet and call onResult with the return value.""" 149 | if self.open: 150 | def task(): 151 | try: 152 | res = func(*args,**kwargs) 153 | except: 154 | onResult(False,failure.Failure()) 155 | else: 156 | onResult(True,res) 157 | self.add(Greenlet.spawn_later(0,task,*args,**kwargs)) 158 | 159 | def stop(self): 160 | """Stop greenlet pool.""" 161 | self.open = False 162 | self.kill(block=False) 163 | self.join() 164 | 165 | def adjustPoolsize(self,minthreads=None,maxthreads=None): 166 | pass 167 | 168 | 169 | class GeventResolver(ThreadedResolver): 170 | """Based on ThreadedResolver, GeventResolver uses gevent to perform name lookups.""" 171 | 172 | def getHostByName(self,name,timeout=(1,3,11,45)): 173 | if timeout: 174 | timeoutDelay = sum(timeout) 175 | else: 176 | timeoutDelay = 60 177 | userDeferred = defer.Deferred() 178 | lookupDeferred = deferToThreadPool( 179 | self.reactor,self.reactor.getThreadPool(),socket.gethostbyname,name) 180 | cancelCall = self.reactor.callLater( 181 | timeoutDelay,self._cleanup,name,lookupDeferred) 182 | self._runningQueries[lookupDeferred] = (userDeferred,cancelCall) 183 | lookupDeferred.addBoth(self._checkTimeout,name,lookupDeferred) 184 | return userDeferred 185 | 186 | 187 | class DelayedCall(object): 188 | """Delayed call proxy for IReactorTime""" 189 | 190 | implements(IDelayedCall) 191 | debug = False 192 | _str = None 193 | 194 | def __init__(self,caller,time,func,a,kw,seconds=runtimeSeconds): 195 | self.caller = caller 196 | self.time = time 197 | self.func = func 198 | self.a = a 199 | self.kw = kw 200 | self.seconds = seconds 201 | self.cancelled = self.called = 0 202 | if self.debug: 203 | self.creator = traceback.format_stack()[:-2] 204 | 205 | def __call__(self): 206 | if not (self.called or self.cancelled): 207 | self.called = 1 208 | self.func(*self.a,**self.kw) 209 | del self.func,self.a,self.kw 210 | 211 | def getTime(self): 212 | return self.time 213 | 214 | def cancel(self): 215 | if self.cancelled: 216 | raise error.AlreadyCancelled 217 | elif self.called: 218 | raise error.AlreadyCalled 219 | else: 220 | self.cancelled = 1 221 | if self.debug: 222 | self._str = str(self) 223 | del self.func,self.a,self.kw 224 | self.caller.cancelCallLater(self) 225 | 226 | def reset(self,secondsFromNow): 227 | if self.cancelled: 228 | raise error.AlreadyCancelled 229 | elif self.called: 230 | raise error.AlreadyCalled 231 | else: 232 | self.time = self.seconds()+secondsFromNow 233 | self.caller.scheduleDelayedCall(self) 234 | 235 | def delay(self,secondsFromLater): 236 | if self.cancelled: 237 | raise error.AlreadyCancelled 238 | elif self.called: 239 | raise error.AlreadyCalled 240 | else: 241 | self.time += secondsFromLater 242 | self.caller.scheduleDelayedCall(self) 243 | 244 | def active(self): 245 | return not (self.cancelled or self.called) 246 | 247 | def __le__(self,other): 248 | return self.time <= other.time 249 | 250 | def __lt__(self,other): 251 | return self.time < other.time 252 | 253 | def __str__(self): 254 | if self._str is not None: 255 | return self._str 256 | if hasattr(self, 'func'): 257 | if hasattr(self.func, 'func_name'): 258 | func = self.func.func_name 259 | if hasattr(self.func, 'im_class'): 260 | func = self.func.im_class.__name__ + '.' + func 261 | else: 262 | func = reflect.safe_repr(self.func) 263 | else: 264 | func = None 265 | now = self.seconds() 266 | L = ['') 281 | return ''.join(L) 282 | 283 | 284 | class Stream(Greenlet,styles.Ephemeral): 285 | 286 | def __init__(self,reactor,selectable,method): 287 | Greenlet.__init__(self) 288 | self.reactor = reactor 289 | self.selectable = selectable 290 | self.method = method 291 | self.wake = Event() 292 | self.wake.set() 293 | self.pause = self.wake.clear 294 | self.resume = self.wake.set 295 | 296 | def _run(self): 297 | selectable = self.selectable 298 | method = self.method 299 | wait = {'doRead':socket.wait_read,'doWrite':socket.wait_write}[method] 300 | try: 301 | fileno = selectable.fileno() 302 | except AttributeError: 303 | why = _NO_FILENO 304 | else: 305 | if fileno == -1: 306 | why = _NO_FILEDESC 307 | else: 308 | why = None 309 | if why is None: 310 | wake = self.wake.wait 311 | try: 312 | while wake(): 313 | wait(fileno) 314 | why = getattr(selectable,method)() 315 | if why: 316 | break 317 | except GreenletExit: 318 | pass 319 | except IOError: # fix 320 | pass 321 | except AttributeError: # fix 322 | pass 323 | except: 324 | why = sys.exc_info()[1] 325 | log.err() 326 | if why: 327 | try: 328 | self.reactor._disconnectSelectable(selectable,why,method=='doRead') 329 | except AttributeError: 330 | pass 331 | if method == 'doRead': 332 | self.reactor.discardReader(selectable) 333 | else: 334 | self.reactor.discardWriter(selectable) 335 | 336 | 337 | class GeventReactor(posixbase.PosixReactorBase): 338 | """Implement gevent-powered reactor based on PosixReactorBase.""" 339 | 340 | implements(IReactorGreenlets) 341 | 342 | def __init__(self,*args,**kwargs): 343 | self.resolver = None 344 | self.greenlet = None 345 | self.threadpool = None 346 | self._reads = {} 347 | self._writes = {} 348 | self._callqueue = [] 349 | self._wake = 0 350 | self._wait = 0 351 | posixbase.PosixReactorBase.__init__(self,*args,**kwargs) 352 | 353 | def mainLoop(self): 354 | """This main loop yields to gevent until the end, handling function calls along the way.""" 355 | self.greenlet = gevent.getcurrent() 356 | callqueue = self._callqueue 357 | seconds = self.seconds 358 | try: 359 | while 1: 360 | self._wait = 0 361 | now = seconds() 362 | if callqueue: 363 | self._wake = delay = callqueue[0].time 364 | delay -= now 365 | else: 366 | self._wake = now+300 367 | delay = 300 368 | try: 369 | self._wait = 1 370 | gevent.sleep(delay if delay > 0 else 0) 371 | except Reschedule: 372 | continue 373 | finally: 374 | self._wait = 0 375 | now = seconds() 376 | while 1: 377 | try: 378 | c = callqueue[0] 379 | except IndexError: 380 | break 381 | if c.time <= now: 382 | del callqueue[0] 383 | try: 384 | c() 385 | except GreenletExit: 386 | raise 387 | except: 388 | log.msg('Unexpected error in main loop.') 389 | log.err() 390 | else: 391 | break 392 | except (GreenletExit,KeyboardInterrupt): 393 | pass 394 | log.msg('Main loop terminated.') 395 | self.fireSystemEvent('shutdown') 396 | 397 | def addReader(self,selectable): 398 | """Add a FileDescriptor for notification of data available to read.""" 399 | try: 400 | self._reads[selectable].resume() 401 | except KeyError: 402 | self._reads[selectable] = g = Stream(self,selectable,'doRead') 403 | g.start() 404 | self.threadpool.add(g) 405 | 406 | def addWriter(self,selectable): 407 | """Add a FileDescriptor for notification of data available to write.""" 408 | try: 409 | self._writes[selectable].resume() 410 | except KeyError: 411 | self._writes[selectable] = g = Stream(self,selectable,'doWrite') 412 | g.start() 413 | self.threadpool.add(g) 414 | 415 | def removeReader(self,selectable): 416 | """Remove a FileDescriptor for notification of data available to read.""" 417 | try: 418 | if selectable.disconnected: 419 | self._reads[selectable].kill(block=False) 420 | del self._reads[selectable] 421 | else: 422 | self._reads[selectable].pause() 423 | except KeyError: 424 | pass 425 | 426 | def removeWriter(self,selectable): 427 | """Remove a FileDescriptor for notification of data available to write.""" 428 | try: 429 | if selectable.disconnected: 430 | self._writes[selectable].kill(block=False) 431 | del self._writes[selectable] 432 | else: 433 | self._writes[selectable].pause() 434 | except KeyError: 435 | pass 436 | 437 | def discardReader(self,selectable): 438 | """Remove a FileDescriptor without checking.""" 439 | try: 440 | del self._reads[selectable] 441 | except KeyError: 442 | pass 443 | 444 | def discardWriter(self,selectable): 445 | """Remove a FileDescriptor without checking.""" 446 | try: 447 | del self._writes[selectable] 448 | except KeyError: 449 | pass 450 | 451 | def getReaders(self): 452 | return self._reads.keys() 453 | 454 | def getWriters(self): 455 | return self._writes.keys() 456 | 457 | def removeAll(self): 458 | return self._removeAll(self._reads,self._writes) 459 | 460 | # IReactorTime 461 | 462 | seconds = staticmethod(runtimeSeconds) 463 | 464 | def callLater(self,delay,func,*args,**kw): 465 | c = DelayedCall(self,self.seconds()+delay,func,args,kw,seconds=self.seconds) 466 | insort(self._callqueue,c) 467 | self.reschedule() 468 | return c 469 | 470 | def getDelayedCalls(self): 471 | return list(self._callqueue) 472 | 473 | def cancelCallLater(self,callID): 474 | warnings.warn('GeventReactor.cancelCallLater is deprecated',DeprecationWarning) 475 | self._callqueue.remove(callID) 476 | self.reschedule() 477 | 478 | # IReactorThreads 479 | 480 | def _initThreads(self): 481 | self.usingGreenlets = self.usingThreads = True 482 | if self.threadpool is None: 483 | self.resolver = GeventResolver(self) 484 | self.threadpool = GeventThreadPool() 485 | self.threadpoolShutdownID = self.addSystemEventTrigger('during','shutdown',self._stopThreadPool) 486 | 487 | def _stopThreadPool(self): 488 | self.threadpoolShutdownID = None 489 | if self.threadpool is not None: 490 | self.threadpool.stop() 491 | self.threadpool = None 492 | 493 | def getThreadPool(self): 494 | return self.threadpool 495 | 496 | def callInThread(self,*args,**kwargs): 497 | self.threadpool.callInThread(*args,**kwargs) 498 | 499 | def callFromThread(self,func,*args,**kw): 500 | c = DelayedCall(self,self.seconds(),func,args,kw,seconds=self.seconds) 501 | insort(self._callqueue,c) 502 | self.reschedule() 503 | return c 504 | 505 | def suggestThreadPoolSize(self,*args,**kwargs): 506 | pass 507 | 508 | # IReactorGreenlets, mirrored from IReactorThreads for backwards-compatibility 509 | 510 | _initGreenlets = _initThreads 511 | _stopGreenletPool = _stopThreadPool 512 | getGreenletPool = getThreadPool 513 | callInGreenlet = callInThread 514 | callFromGreenlet = callFromThread 515 | suggestGreenletPoolSize = suggestThreadPoolSize 516 | 517 | # IReactorCore 518 | 519 | def stop(self): 520 | self._callqueue.insert(0,DelayedCall(self,0,gevent.sleep,(),{},seconds=self.seconds)) 521 | gevent.kill(self.greenlet) 522 | 523 | def reschedule(self): 524 | if self._wait and self._callqueue and self._callqueue[0].time < self._wake: 525 | gevent.kill(self.greenlet,Reschedule) 526 | 527 | def scheduleDelayedCall(self,c): 528 | try: 529 | self._callqueue.remove(c) 530 | except ValueError: 531 | pass 532 | insort(self._callqueue,c) 533 | self.reschedule() 534 | return c 535 | 536 | 537 | def install(): 538 | """Configure the twisted mainloop to be run using geventreactor.""" 539 | reactor = GeventReactor() 540 | from twisted.internet.main import installReactor 541 | installReactor(reactor) 542 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | setup( 6 | name = 'geventreactor', 7 | version = '0.1.0', 8 | description = 'Twisted reactor based on gevent', 9 | long_description = 'geventreactor is a gevent-powered Twisted reactor whose goal is to enable mixing of gevent- and Twisted-oriented code, allowing developers to benefit from the performance of libevent and greenlet while retaining access to the extensive functionality of Twisted.', 10 | license = 'MIT', 11 | author = 'Jiang Yio', 12 | author_email = 'inportb@gmail.com', 13 | url = 'http://github.com/jyio/geventreactor', 14 | packages = find_packages(), 15 | install_requires = ['gevent'] 16 | ) 17 | --------------------------------------------------------------------------------