Opened 3 years ago

Closed 3 years ago

#5485 defect closed duplicate (duplicate)

Canceling a pending TCP4ClientEndpoint connection leads to an AlreadyCalledError

Reported by: jssebastian Owned by:
Priority: normal Milestone:
Component: core Keywords: endpoints cancel
Cc: Branch:
Author: Launchpad Bug:

Description

It seems that if a tcp connection is hanging (e.g. because the destiantion host sends no reply), attempting to cancel it will lead to a spurious AlreadyCalledError.

Here is a small test case that triggers this bug:

from twisted.internet import reactor
from twisted.internet.protocol import Factory, Protocol
from twisted.internet.endpoints import TCP4ClientEndpoint
from twisted.internet import defer

class Greeter(Protocol):
    pass

class GreeterFactory(Factory):
    def buildProtocol(self, addr):
        return Greeter()

def gotProtocol(p):
    print "Got protocol"
    
def connectionFailed(f):
    print "Connection failed"

def timeout(d):
    print "Got timeout"
    if not d.called:
        d.cancel()
        

#defer.setDebugging(True)
point = TCP4ClientEndpoint(reactor, "www.google.com", 666)
d = point.connect(GreeterFactory())
d.addCallback(gotProtocol)
d.addErrback(connectionFailed)
reactor.callLater(10, timeout, d)
reactor.run()

Notice I even test d.called before calling d.cancel().
www.google.com does not respond on port 666, and after 10 seconds I get:

$python alreadycalled.py
Got timeout
Connection failed
Unhandled Error
Traceback (most recent call last):
  File "alreadycalled.py", line 28, in <module>
    reactor.run()
  File "/usr/local/lib/python2.6/dist-packages/Twisted-11.1.0-py2.6-linux-x86_64.egg/twisted/internet/base.py", line 1169, in run
    self.mainLoop()
  File "/usr/local/lib/python2.6/dist-packages/Twisted-11.1.0-py2.6-linux-x86_64.egg/twisted/internet/base.py", line 1178, in mainLoop
    self.runUntilCurrent()
--- <exception caught here> ---
  File "/usr/local/lib/python2.6/dist-packages/Twisted-11.1.0-py2.6-linux-x86_64.egg/twisted/internet/base.py", line 800, in runUntilCurrent
    call.func(*call.args, **call.kw)
  File "alreadycalled.py", line 21, in timeout
    d.cancel()
  File "/usr/local/lib/python2.6/dist-packages/Twisted-11.1.0-py2.6-linux-x86_64.egg/twisted/internet/defer.py", line 427, in cancel
    canceller(self)
  File "/usr/local/lib/python2.6/dist-packages/Twisted-11.1.0-py2.6-linux-x86_64.egg/twisted/internet/endpoints.py", line 261, in _canceller
    error.ConnectingCancelledError(connector.getDestination()))
  File "/usr/local/lib/python2.6/dist-packages/Twisted-11.1.0-py2.6-linux-x86_64.egg/twisted/internet/defer.py", line 391, in errback
    self._startRunCallbacks(fail)
  File "/usr/local/lib/python2.6/dist-packages/Twisted-11.1.0-py2.6-linux-x86_64.egg/twisted/internet/defer.py", line 451, in _startRunCallbacks
    raise AlreadyCalledError
twisted.internet.defer.AlreadyCalledError: 

Notice that the connection failed message is printed *after* the cancel() call.

Tested on:

twisted 11.1.0, python 2.6 on ubuntu 10.04 64 bit

twisted 11.1.0, python 2.7 on ubuntu 11.10 64 bit

Change History (2)

comment:1 Changed 3 years ago by exarkun

  • Keywords endpoints added; bug endpoint removed

Canceling the connection attempt leads to the Connector delivering connection failed notification to the factory, which the endpoint implementation intercepts. The endpoint implementation then tries to errback its result Deferred which is mid-cancelation at this point. Problems ensue.

The unit tests don't catch this because they use a fake Connector implementation that doesn't actually implement any Connector-like behavior at all.

comment:2 Changed 3 years ago by exarkun

  • Resolution set to duplicate
  • Status changed from new to closed

Duplicate of #4710.

Note: See TracTickets for help on using tickets.