From 14c7141094b703e67d8915e92417000b99e30e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dhionel=20D=C3=ADaz?= Date: Fri, 10 Jun 2011 17:28:32 -0430 Subject: [PATCH] Threadsafety improvements using thread local storage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The _opener global object defined in the _opener module was wrapped in a subclass of threading.local to improve mechanize threadsafety. With this change that object is no longer shared between threads and is not required to synchronize the execution of urlopen and urlretrieve. This modified version of mechanize was used successfully as backend for a stress test based on the multi-mechanize framework (*) in which up to 512 agents per user group were active. (*) http://code.google.com/p/multi-mechanize/ Signed-off-by: Dhionel Díaz --- mechanize/_opener.py | 37 +++++++++++++++---------------------- mechanize/_urllib2_fork.py | 20 +++++++++++++------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/mechanize/_opener.py b/mechanize/_opener.py index ad8412d..d8f3aec 100644 --- a/mechanize/_opener.py +++ b/mechanize/_opener.py @@ -412,31 +412,24 @@ def build_opener(self, *handlers): build_opener = OpenerFactory().build_opener -_opener = None -urlopen_lock = _threading.Lock() +class _tls(_threading.local): + _opener = None + +_tls00 = _tls() + def urlopen(url, data=None, timeout=_sockettimeout._GLOBAL_DEFAULT_TIMEOUT): - global _opener - if _opener is None: - urlopen_lock.acquire() - try: - if _opener is None: - _opener = build_opener() - finally: - urlopen_lock.release() - return _opener.open(url, data, timeout) + global _tls00 + if _tls00._opener is None: + _tls00._opener = build_opener() + return _tls00._opener.open(url, data, timeout) def urlretrieve(url, filename=None, reporthook=None, data=None, timeout=_sockettimeout._GLOBAL_DEFAULT_TIMEOUT): - global _opener - if _opener is None: - urlopen_lock.acquire() - try: - if _opener is None: - _opener = build_opener() - finally: - urlopen_lock.release() - return _opener.retrieve(url, filename, reporthook, data, timeout) + global _tls00 + if _tls00._opener is None: + _tls00._opener = build_opener() + return _tls00._opener.retrieve(url, filename, reporthook, data, timeout) def install_opener(opener): - global _opener - _opener = opener + global _tls00 + _tls00._opener = opener diff --git a/mechanize/_urllib2_fork.py b/mechanize/_urllib2_fork.py index d0cfe38..9bf5713 100644 --- a/mechanize/_urllib2_fork.py +++ b/mechanize/_urllib2_fork.py @@ -110,16 +110,22 @@ def splithost(url): # used in User-Agent header sent __version__ = sys.version[:3] -_opener = None +import threading + +class _tls(threading.local): + _opener = None + +_tls00 = _tls() + def urlopen(url, data=None, timeout=_sockettimeout._GLOBAL_DEFAULT_TIMEOUT): - global _opener - if _opener is None: - _opener = build_opener() - return _opener.open(url, data, timeout) + global _tls00 + if _tls00._opener is None: + _tls00._opener = build_opener() + return _tls00._opener.open(url, data, timeout) def install_opener(opener): - global _opener - _opener = opener + global _tls00 + _tls00._opener = opener # copied from cookielib.py _cut_port_re = re.compile(r":\d+$")