On Nov 16, 2013, at 7:09 AM, Tom van Neerijnen wrote:

> Hi all
> 
> I'm building a simple TCP load balancer based on a code snippet from Glyph on 
> SO: 
> http://stackoverflow.com/questions/4096061/general-question-regarding-wether-or-not-use-twisted-in-tcp-proxy-project
> 
> It's served me well but I can't work out how to convert Glyphs round robin 
> retrieval of the server endpoint into an async balancing decision in the 
> buildProtocol method of the Factory. If I return a deferred here it fails 
> with an AttributeError: Deferred instance has no attribute 'makeConnection'.
> 
> Currently I'm working around this by running a separate management loop that 
> periodically updates a dictionary with all the data necessary to make my 
> routing decision so that I can do it without a deferred. This worries me 
> because I may be making my decision on slightly stale data and I'd really 
> like this to be a real time decision as the connection comes in. Does anyone 
> have a clever way of doing this?
> 


Hi Tom,

One possibly unexpected aspect of using @inlineCallbacks is that the decorated 
function itself returns a Deferred. This is why you see the 
AttributeError...the machinery calling buildProtocol expects an IProtocol 
instance (or None), and the function is returning a Deferred.   
`defer.returnValue()` is provided to the callback on that Deferred, not as a 
direct return value from the decorated function.

If you want to make the routing decision when the client connects, then you 
could push the decision-making process down into the Protocol itself.

Here's a quick mockup overriding connectionMade in a ProxyServer protocol 
subclass. It calls the factory routing function (which may or may not return a 
deferred), and connects the proxy once the decision has been made.


from twisted.internet.protocol import Factory
from twisted.protocols.portforward import ProxyServer


class Balancer(Factory):
    protocol = RoutingProxyServer
    routing_func = port_routing_decision_async


class RoutingProxyServer(ProxyServer):

    def connectionMade(self):
        # Don't read anything from the connecting client until we have
        # somewhere to send it to.
        self.transport.pauseProducing()

        client = self.clientProtocolFactory()
        client.setServer(self)
        
        if self.reactor is None:
            from twisted.internet import reactor
            self.reactor = reactor
        
        def connectProxy(host, port):
            self.reactor.connectTCP(host, port, client)

        d = maybeDeferred(self.factory.routing_func)
        d.addCallback(connectProxy)
        d.addErrback(log.err)


Lucas





> An example is below. The hashed out buildProtocol is a synchronous decision 
> which works. Thanks in advance!
> 
> from twisted.internet.protocol import Factory
> from twisted.protocols.portforward import ProxyFactory
> from twisted.internet import reactor, defer
> import random
> 
> from twisted.python import log
> import sys
> log.startLogging(sys.stderr)
> 
> local_ports = set([1024, 1025])
> 
> def port_routing_decision_sync():
>     return random.choice(list(local_ports))
> 
> def port_routing_decision_async():
>     d = defer.Deferred()
>     reactor.callLater(1, d.callback, port_routing_decision_sync())
>     return d
> 
> class Balancer(Factory):
>     # def buildProtocol(self, addr):
>     #     port = port_routing_decision_sync()
>     #     print "connecting to local port {}".format(port)
>     #     return ProxyFactory("127.0.0.1", port).buildProtocol(addr)
> 
>     @defer.inlineCallbacks
>     def buildProtocol(self, addr):
>         port = yield port_routing_decision_async()
>         print "connecting to local port {}".format(port)
>         defer.returnValue(ProxyFactory("127.0.0.1", port).buildProtocol(addr))
> 
> def main():
>     factory = Balancer()
>     reactor.listenTCP(5678, factory)
>     reactor.run()
> 
> if __name__ == "__main__":
>     main()

_______________________________________________
Twisted-Python mailing list
Twisted-Python@twistedmatrix.com
http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python

Reply via email to