Merge lp:~cjwatson/txpkgupload/fix-leak into lp:~lazr-developers/txpkgupload/trunk

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 21
Merged at revision: 21
Proposed branch: lp:~cjwatson/txpkgupload/fix-leak
Merge into: lp:~lazr-developers/txpkgupload/trunk
Diff against target: 94 lines (+22/-16)
2 files modified
src/txpkgupload/plugin.py (+5/-1)
src/txpkgupload/twistedsftp.py (+17/-15)
To merge this branch: bzr merge lp:~cjwatson/txpkgupload/fix-leak
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+247276@code.launchpad.net

Commit message

Fix memory leak when closing SFTP connections.

Description of the change

Yesterday evening there was an outage due to txpkgupload having leaked to the point where it was using rather more than 4GB of memory. I looked into this a bit today and found that I could make it leak just by repeatedly initiating SFTP connections. This is happening because it registers a global handler for SFTPClosed on every connection and never unregisters it. Rather than going through the component registry for this, it seems easier to just subclass FileTransferServer, and with this change memory use remains stable when hammering the server with consecutive no-op SFTP connections in a loop.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/txpkgupload/plugin.py'
2--- src/txpkgupload/plugin.py 2015-01-21 12:50:43 +0000
3+++ src/txpkgupload/plugin.py 2015-01-22 11:33:13 +0000
4@@ -43,7 +43,10 @@
5 ReadyService,
6 )
7 from txpkgupload.twistedftp import FTPServiceFactory
8-from txpkgupload.twistedsftp import SFTPServer
9+from txpkgupload.twistedsftp import (
10+ PkgUploadFileTransferServer,
11+ SFTPServer,
12+ )
13
14
15 # Ensure that formencode does not translate strings; there are encoding issues
16@@ -155,6 +158,7 @@
17 LaunchpadAvatar.__init__(self, user_dict)
18 self.fs_root = fs_root
19 self.temp_dir = temp_dir
20+ self.subsystemLookup["sftp"] = PkgUploadFileTransferServer
21
22
23 class Realm:
24
25=== modified file 'src/txpkgupload/twistedsftp.py'
26--- src/txpkgupload/twistedsftp.py 2015-01-21 12:59:40 +0000
27+++ src/txpkgupload/twistedsftp.py 2015-01-22 11:33:13 +0000
28@@ -13,16 +13,14 @@
29 import os
30 import tempfile
31
32-from lazr.sshserver.events import SFTPClosed
33-from lazr.sshserver.sftp import FileIsADirectory
34+from lazr.sshserver.sftp import (
35+ FileIsADirectory,
36+ FileTransferServer,
37+ )
38 from twisted.conch.interfaces import (
39 ISFTPFile,
40 ISFTPServer,
41 )
42-from zope.component import (
43- adapter,
44- provideHandler,
45- )
46 from zope.interface import implements
47
48 from txpkgupload.filesystem import UploadFileSystem
49@@ -35,15 +33,12 @@
50 implements(ISFTPServer)
51
52 def __init__(self, avatar):
53- provideHandler(self.connectionClosed)
54 self._avatar = avatar
55 self._fs_root = avatar.fs_root
56 self.uploadfilesystem = UploadFileSystem(
57 tempfile.mkdtemp(dir=avatar.temp_dir))
58 self._current_upload = self.uploadfilesystem.rootpath
59 os.chmod(self._current_upload, 0770)
60- self.hook = Hooks(self._fs_root, perms='g+rws', prefix='-sftp')
61- self.hook.new_client_hook(self._current_upload, 0, 0)
62
63 def gotVersion(self, other_version, ext_data):
64 return {}
65@@ -101,12 +96,6 @@
66 return self.uploadfilesystem._full(
67 self.uploadfilesystem._sanitize(filename))
68
69- @adapter(SFTPClosed)
70- def connectionClosed(self, event):
71- if event.avatar is not self._avatar:
72- return
73- self.hook.client_done_hook(self._current_upload, 0, 0)
74-
75
76 class SFTPFile:
77
78@@ -138,3 +127,16 @@
79
80 def setAttrs(self, attr):
81 pass
82+
83+
84+class PkgUploadFileTransferServer(FileTransferServer):
85+
86+ def __init__(self, data=None, avatar=None):
87+ FileTransferServer.__init__(self, data=data, avatar=avatar)
88+ self.hook = Hooks(self.client._fs_root, perms='g+rws', prefix='-sftp')
89+ self.hook.new_client_hook(self.client._current_upload, 0, 0)
90+
91+ def connectionLost(self, reason):
92+ if self.hook is not None:
93+ self.hook.client_done_hook(self.client._current_upload, 0, 0)
94+ self.hook = None

Subscribers

People subscribed via source and target branches

to all changes: