Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5119858
  • 博文数量: 921
  • 博客积分: 16037
  • 博客等级: 上将
  • 技术积分: 8469
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-05 02:08
文章分类

全部博文(921)

文章存档

2020年(1)

2019年(3)

2018年(3)

2017年(6)

2016年(47)

2015年(72)

2014年(25)

2013年(72)

2012年(125)

2011年(182)

2010年(42)

2009年(14)

2008年(85)

2007年(89)

2006年(155)

分类: Python/Ruby

2012-04-19 23:59:11

I'm trying to implement a service with Twisted that's fairly close to the "finger" tutorial found here: http://twistedmatrix.com/documents/current/core/howto/tutorial/intro.html

I've got a basic.LineListener waiting for a command and then executing it, then I have a client connecting and issuing commands. Trouble is that the command sometimes needs to execute something else and I'm using python's subprocess module for that. It's not just that calls to communicate() are hanging, that's a normal subprocess issue and I know how to get past it. It's that subprocess.Popen calls are hanging.

Here's the twisted server code:

  1. import subprocess
  2. class MyProtocol(basic.LineReceiver):
  3.     def lineReceived(self, line):
  4.         self.go()
  5.     
  6.     def go(self):
  7.         def writeResponse(message):
  8.             self.transport.write(message + '\r\n')
  9.             self.transport.loseConnection()
  10.         threads.deferToThread(self.factory.action).addCallback(writeResponse)
  11.     def connectionMade(self):
  12.         self.lines = []

  13. class ActionService(service.Service):
  14.     def __init__(self, **kwargs):
  15.         pass
  16.         #self.users = kwargs
  17.     def action(self):
  18.         print "launching subprocess"
  19.         sys.stdout.flush()
  20.         p = subprocess.Popen(["ls"], stderr=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
  21.         print "launched subprocess, trying to communicate..."
  22.         sys.stdout.flush()
  23.         p.communicate()
  24.         print "returning"
  25.         sys.stdout.flush()
  26.         return "%032d" % (0)
  27.     
  28.     def getActionFactory(self):
  29.         f = protocol.ServerFactory()
  30.         f.protocol = MyProtocol
  31.         f.action = self.action
  32.         return f

  33. reactor.suggestThreadPoolSize(300)
  34. application = service.Application('Action', uid=0, gid=0)
  35. f = ActionService()
  36. serviceCollection = service.IServiceCollection(application)
  37. internet.TCPServer(31337,f.getActionFactory()).setServiceParent(serviceCollection)

...and here's some client code:


 

  1. #!/usr/bin/python
  2. import time
  3. import threading
  4. import socket

  5. def connectAction(host):
  6.     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  7.     s.connect((host, 31337))
  8.     s.send("asdf\r\n")
  9.     resp = s.recv(32)
  10.     s.close()
  11.     return resp

  12. class sscceThread(threading.Thread):
  13.     def __init__(self, host):
  14.         self.host = host
  15.         threading.Thread.__init__(self)

  16.     def run(self):
  17.         connectAction(self.host)
  18. def main():
  19.     threads = []
  20.     for i in range(0, 1000):
  21.         for j in range(0,5):
  22.             t = sscceThread("localhost")
  23.             t.start()
  24.             threads.append(t)
  25.         for t in threads:
  26.             t.join()
  27.         print i
  28.         time.sleep(1)
  29.        #print i

  30. if __name__ == "__main__":
  31.     main()

Start the service by running:

  1. twistd -y sscce_twisted_service.py -l twistdLog; tail -f twistdLog

And run the client by running:

  1. ./sscce_twisted_client.py

You should see the client go for a couple of iterations (I've seen it go as many as 10) and then hang. The client code contains a 1-second sleep so that you can tell the difference between twisted log entries from each iteration and on the one that hangs you'll see something like this in the twisted log:


 

  1. 2009-12-22 11:18:47-0800 [MyProtocol,55,127.0.0.1] launching subprocess
  2. 2009-12-22 11:18:47-0800 [MyProtocol,56,127.0.0.1] launching subprocess
  3. 2009-12-22 11:18:47-0800 [MyProtocol,55,127.0.0.1] launched subprocess, trying to communicate...
  4. 2009-12-22 11:18:47-0800 [MyProtocol,57,127.0.0.1] launching subprocess
  5. 2009-12-22 11:18:47-0800 [MyProtocol,58,127.0.0.1] launching subprocess
  6. 2009-12-22 11:18:47-0800 [MyProtocol,56,127.0.0.1] launched subprocess, trying to communicate...
  7. 2009-12-22 11:18:47-0800 [MyProtocol,55,127.0.0.1] returning
  8. 2009-12-22 11:18:47-0800 [MyProtocol,57,127.0.0.1] launching subprocess
  9. 2009-12-22 11:18:47-0800 [MyProtocol,56,127.0.0.1] launching subprocess returning
  10. 2009-12-22 11:18:47-0800 [MyProtocol,59,127.0.0.1] launching subprocess
  11. 2009-12-22 11:18:47-0800 [MyProtocol,58,127.0.0.1] launched subprocess, trying to communicate...
  12. 2009-12-22 11:18:47-0800 [MyProtocol,58,127.0.0.1] returning
  13. 2009-12-22 11:18:47-0800 [MyProtocol,59,127.0.0.1] launched subprocess, trying to communicate...
  14. 2009-12-22 11:18:47-0800 [MyProtocol,59,127.0.0.1] returning

Of particular note is MyProtocol,57. It says it was about to try launching the subprocess but it never printed the "launched subprocess, trying to communicate" line. I think it must have hung there.

1 Answer

 

As mg said in his comment, don't use the subprocess module. On POSIX platforms, it's necessary (more or less) to handle the SIGCHLD signal to deal with child processes that exit. Since there can only be one SIGCHLD handler, multiple libraries generally won't cooperate. Twisted's child process support and the subprocess module's support conflict. Either use Twisted's support (see http://twistedmatrix.com/documents/current/core/howto/process.html) or disable Twisted's support by passing installSignalHandlers=False to reactor.run (I recommend the former, as subprocess presents blocking interfaces which don't integrate well into Twisted-based applications).

==============================================================================

Twisted also gives you control over inherited FDs, which is sometimes quite handy. With subprocess, if you want to use stdout/stderr, you must inherit all FDs, and if one of those is a listen socket, then the child holds it open indefinitely if the parent terminates uncleanly. But one thing to be aware of: if you intend to deploy this on Windows, you will need the Python for Windows extensions, which are not available by default. I found this out the hard way, and had to rewrite to use subprocess + reactor.iterate, since deploying additional packages wasn't an option at the time. – 

==============================================================================

Note that this issue has been fixed in Twisted's trunk, but is not yet present in a release. It will be in Twisted 10.1, when that is released. However, spawnProcess is still considerably more flexible and robust than the subprocess module. For example, subprocess invokes "select", even if Twisted is set up to use a more efficient event loop, which means you may be unable to spawn subprocesses in a server with a large number of active connections, and JP's comment about blocking stuff not integrating well into Twisted is still well-taken. – 

 

 

 

from:

阅读(1346) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~