| | 220 | |
| | 221 | def setUp(self): |
| | 222 | """ |
| | 223 | Create a weakvaluedict in order to hold potentially leaked objects, |
| | 224 | and another dict to hold potentially destroyed objects. |
| | 225 | """ |
| | 226 | self._leaks = weakref.WeakValueDictionary() |
| | 227 | self._survivors = {} |
| | 228 | |
| | 229 | def _watchForLeaks(self, *args): |
| | 230 | """ |
| | 231 | Watch the given objects for leaks, by creating weakrefs to them. |
| | 232 | """ |
| | 233 | for obj in args: |
| | 234 | key = id(obj), repr(obj) |
| | 235 | self._leaks[key] = obj |
| | 236 | |
| | 237 | def _watchForSurvival(self, *args): |
| | 238 | """ |
| | 239 | Watch the given objects for survival, by creating weakrefs to them. |
| | 240 | """ |
| | 241 | for obj in args: |
| | 242 | key = id(obj), repr(obj) |
| | 243 | self._survivors[key] = weakref.ref(obj) |
| | 244 | |
| | 245 | def _assertLeaks(self): |
| | 246 | """ |
| | 247 | Assert that all objects watched for leaks have been destroyed. |
| | 248 | """ |
| | 249 | # Trigger cycle breaking |
| | 250 | gc.collect() |
| | 251 | if len(self._leaks): |
| | 252 | self.fail("%d objects have leaked: %s" % ( |
| | 253 | len(self._leaks), |
| | 254 | ", ".join([key[1] for key in self._leaks]) |
| | 255 | )) |
| | 256 | |
| | 257 | def _assertSurvival(self): |
| | 258 | """ |
| | 259 | Assert that all objects watched for survival have survived. |
| | 260 | """ |
| | 261 | # Trigger cycle breaking |
| | 262 | gc.collect() |
| | 263 | dead = [] |
| | 264 | for (id_, repr_), ref in self._survivors.items(): |
| | 265 | if ref() is None: |
| | 266 | dead.append(repr_) |
| | 267 | if dead: |
| | 268 | self.fail("%d objects should have survived " |
| | 269 | "but have been destroyed: %s" % (len(dead), ", ".join(dead))) |
| | 270 | |
| | 271 | |
| | 272 | def _allocateStuff(self): |
| | 273 | """ |
| | 274 | Allocate some objects so as to try to overwrite dead objects with other |
| | 275 | stuff. Not guaranteed to work but at least we try :-) |
| | 276 | """ |
| | 277 | # Reclaim memory, then fill it. We create a lot of plain objects so |
| | 278 | # that the main allocator is exercised. |
| | 279 | gc.collect() |
| | 280 | class _Dummy(object): |
| | 281 | pass |
| | 282 | [_Dummy() for i in xrange(10000)] |
| | 372 | def test_successfulCallbackReference(self): |
| | 373 | """ |
| | 374 | Check that successful callbacks aren't leaked. |
| | 375 | """ |
| | 376 | newEventBase = libevent.EventBase() |
| | 377 | def cb(fd, events, obj): |
| | 378 | pass |
| | 379 | self._watchForLeaks(cb) |
| | 380 | timer = newEventBase.createTimer(cb) |
| | 381 | timer.addToLoop(0.002) |
| | 382 | newEventBase.dispatch() |
| | 383 | |
| | 384 | del cb, timer |
| | 385 | self._assertLeaks() |
| | 386 | |
| | 387 | def test_failedCallbackReference(self): |
| | 388 | """ |
| | 389 | Check that failed callbacks aren't leaked. |
| | 390 | """ |
| | 391 | newEventBase = libevent.EventBase() |
| | 392 | def eb(fd, events, obj): |
| | 393 | raise RuntimeError("foo") |
| | 394 | self._watchForLeaks(eb) |
| | 395 | timer = newEventBase.createTimer(eb) |
| | 396 | timer.addToLoop(0.002) |
| | 397 | self.assertRaises(RuntimeError, newEventBase.dispatch) |
| | 398 | |
| | 399 | del eb, timer |
| | 400 | self._assertLeaks() |
| | 401 | |
| | 402 | def test_unfiredCallbackReference(self): |
| | 403 | """ |
| | 404 | Check that unfired callbacks aren't leaked when the eventBase is |
| | 405 | destroyed. |
| | 406 | """ |
| | 407 | newEventBase = libevent.EventBase() |
| | 408 | def cb(fd, events, obj): |
| | 409 | pass |
| | 410 | self._watchForLeaks(cb) |
| | 411 | timer = newEventBase.createTimer(cb) |
| | 412 | timer.addToLoop(1) |
| | 413 | |
| | 414 | del cb, timer, newEventBase |
| | 415 | self._assertLeaks() |
| | 416 | |
| | 417 | def test_callbackReference(self): |
| | 418 | """ |
| | 419 | Check that a simple unregistered callback doesn't leak. |
| | 420 | """ |
| | 421 | newEventBase = libevent.EventBase() |
| | 422 | def cb(fd, events, obj): |
| | 423 | pass |
| | 424 | timer = newEventBase.createTimer(cb) |
| | 425 | self._watchForLeaks(cb) |
| | 426 | |
| | 427 | del cb, timer |
| | 428 | self._assertLeaks() |
| | 429 | |
| | 430 | def test_callbackExceptionReference(self): |
| | 431 | """ |
| | 432 | Check that exceptions propagated from callbacks aren't leaked. |
| | 433 | """ |
| | 434 | # Custom subclass so that weakref's are possible |
| | 435 | class _Exception(RuntimeError): |
| | 436 | pass |
| | 437 | exc = [None] |
| | 438 | newEventBase = libevent.EventBase() |
| | 439 | def eb(fd, events, obj): |
| | 440 | exc[0] = _Exception("foo") |
| | 441 | raise exc[0] |
| | 442 | timer = newEventBase.createTimer(eb) |
| | 443 | timer.addToLoop(0.002) |
| | 444 | self.assertRaises(RuntimeError, newEventBase.dispatch) |
| | 445 | self._watchForLeaks(exc[0]) |
| | 446 | |
| | 447 | del exc[0] |
| | 448 | self._assertLeaks() |
| | 449 | |
| | 450 | def test_callbackSurvival(self): |
| | 451 | """ |
| | 452 | Check that a registered callback survives even when the local reference |
| | 453 | dies. |
| | 454 | """ |
| | 455 | newEventBase = libevent.EventBase() |
| | 456 | def cb(fd, events, obj): |
| | 457 | pass |
| | 458 | timer = newEventBase.createTimer(cb) |
| | 459 | timer.addToLoop(1) |
| | 460 | self._watchForSurvival(cb) |
| | 461 | |
| | 462 | del cb, timer |
| | 463 | self._assertSurvival() |
| | 464 | |
| | 465 | def test_persistentCallbackSurvival(self): |
| | 466 | """ |
| | 467 | Check that a persistent callback survives after been fired. |
| | 468 | """ |
| | 469 | rfd, wfd = os.pipe() |
| | 470 | newEventBase = libevent.EventBase() |
| | 471 | def cb(fd, events, obj): |
| | 472 | newEventBase.loopExit(0) |
| | 473 | timer = newEventBase.createEvent(rfd, |
| | 474 | libevent.EV_READ | libevent.EV_PERSIST, cb) |
| | 475 | timer.addToLoop() |
| | 476 | os.write(wfd, " ") |
| | 477 | newEventBase.dispatch() |
| | 478 | self._watchForSurvival(cb) |
| | 479 | |
| | 480 | del cb, timer |
| | 481 | self._assertSurvival() |
| | 482 | |
| | 483 | def test_persistentFailedCallbackSurvival(self): |
| | 484 | """ |
| | 485 | Check that a persistent callback survives after raising an exception. |
| | 486 | """ |
| | 487 | rfd, wfd = os.pipe() |
| | 488 | newEventBase = libevent.EventBase() |
| | 489 | def cb(fd, events, obj): |
| | 490 | newEventBase.loopExit(0) |
| | 491 | raise RuntimeError("foo") |
| | 492 | timer = newEventBase.createEvent(rfd, |
| | 493 | libevent.EV_READ | libevent.EV_PERSIST, cb) |
| | 494 | timer.addToLoop() |
| | 495 | os.write(wfd, " ") |
| | 496 | self.assertRaises(RuntimeError, newEventBase.dispatch) |
| | 497 | self._watchForSurvival(cb) |
| | 498 | |
| | 499 | del cb, timer |
| | 500 | self._assertSurvival() |
| | 501 | |
| | 502 | def test_persistentCallbackReference(self): |
| | 503 | """ |
| | 504 | Check that a persistent callback doesn't leak when the eventBase |
| | 505 | is destroyed. |
| | 506 | """ |
| | 507 | rfd, wfd = os.pipe() |
| | 508 | newEventBase = libevent.EventBase() |
| | 509 | def cb(fd, events, obj): |
| | 510 | newEventBase.loopExit(0) |
| | 511 | timer = newEventBase.createEvent(rfd, |
| | 512 | libevent.EV_READ | libevent.EV_PERSIST, cb) |
| | 513 | timer.addToLoop() |
| | 514 | os.write(wfd, " ") |
| | 515 | newEventBase.dispatch() |
| | 516 | self._watchForLeaks(cb) |
| | 517 | |
| | 518 | newEventBase = None |
| | 519 | del cb, timer |
| | 520 | self._assertLeaks() |
| | 521 | |
| | 522 | def test_dispatchedEventRefCount(self): |
| | 523 | """ |
| | 524 | Check that dispatched event refcounts don't grow. |
| | 525 | """ |
| | 526 | newEventBase = libevent.EventBase() |
| | 527 | def cb(fd, events, obj): |
| | 528 | pass |
| | 529 | timer = newEventBase.createTimer(cb) |
| | 530 | orig = sys.getrefcount(timer) |
| | 531 | timer.addToLoop(0.01) |
| | 532 | newEventBase.dispatch() |
| | 533 | # Perhaps some dead cycles involve our object -> break them |
| | 534 | gc.collect() |
| | 535 | self.assertEquals(orig, sys.getrefcount(timer)) |