There is a bug in current head-of-code sAsync that causes startup() to run more than once unless the user explicitly takes action to avoid this.
I found this out when I wrapped up a Broker object in a Twisted Service and was running the startup() method from the Service's startService() method. However, the very first method call to a @transact-decorated method would re-call the startup() method.
The reason for this is that database.py:transact.substituteFunction() has the following code:
if hasattr(self, 'connection') and getattr(self, 'ranStart', False):
# We already have a connection, let's get right to the transaction
However, when startup() is run manually ahead-of-time (that is, before the first @transact-decorated method is run), self.connection is defined but self.ranStart is not. This is only set in the started() callback, which is unsuitable as a general-purpose callback for the startup() method.
The net result of this is that the code falls through to the else clause at the bottom of substituteFunction():
else:
# We need to start things up before doing this first transaction
d = defer.maybeDeferred(self.startup)
self._transactionStartupDeferred = d
d.addCallback(started)
d.addCallback(lambda _: doTransaction(useSession))
This re-runs the setup() method.
A workaround is to ensure that you manually set the ranStart attribute in your own code; for example, in the context of a Service:
class SASyncBrokerService ( service.Service ):
def __init__ ( self, broker ):
self.broker = broker
def startService ( self ):
service.Service.startService ( self )
d = self.broker.startup()
d.addCallback ( self.brokerStarted )
return d
def brokerStarted ( self, r ):
self.broker.ranStart = True
return r