Permalink
Browse files

urlrequest: rework urlrequest to ensure the object is not GC during t…

…he execution

+ fix callbacks not called back in the clock
+ ensure all the queue events are dispatched
+ use WeakMethod
+ add unittest

refs #678
  • Loading branch information...
1 parent a237bde commit b07f6371007c19db05ed7590d7ad1e5331dacc31 @tito tito committed Sep 27, 2012
Showing with 91 additions and 8 deletions.
  1. +36 −8 kivy/network/urlrequest.py
  2. +55 −0 kivy/tests/test_urlrequest.py
@@ -69,6 +69,11 @@ def bug_posted(req, result):
from urlparse import urlparse
from kivy.clock import Clock
+from kivy.weakmethod import WeakMethod
+
+
+# list to save UrlRequest and prevent GC on un-referenced objects
+g_requests = []
class UrlRequest(Thread):
@@ -113,9 +118,9 @@ def __init__(self, url, on_success=None, on_error=None, on_progress=None,
self._queue = deque()
self._trigger_result = Clock.create_trigger(self._dispatch_result, 0)
self.daemon = True
- self.on_success = on_success
- self.on_error = on_error
- self.on_progress = on_progress
+ self.on_success = WeakMethod(on_success) if on_success else None
+ self.on_error = WeakMethod(on_error) if on_error else None
+ self.on_progress = WeakMethod(on_progress) if on_progress else None
self._result = None
self._error = None
self._is_finished = False
@@ -135,6 +140,9 @@ def __init__(self, url, on_success=None, on_error=None, on_progress=None,
#: Request headers passed in __init__
self.req_headers = req_headers
+ # save our request to prevent GC
+ g_requests.append(self)
+
self.start()
def run(self):
@@ -156,7 +164,16 @@ def run(self):
q(('success', resp, result))
# using trigger can result in a missed on_success event
- self._dispatch_result(0)
+ self._trigger_result()
+
+ # clean ourself when the queue is empty
+ while len(self._queue):
+ sleep(.1)
+ self._trigger_result()
+
+ # ok, authorize the GC to clean us.
+ if self in g_requests:
+ g_requests.remove(self)
def _fetch_url(self, url, body, headers, q):
# Parse and fetch the current url
@@ -222,8 +239,8 @@ def _fetch_url(self, url, body, headers, q):
trigger()
# ensure that restults are dispatch for the last chunk,
# avaoid trigger
- self._dispatch_result(0)
q(('progress', resp, (bytes_so_far, total_size)))
+ trigger()
else:
result = resp.read()
req.close()
@@ -277,15 +294,21 @@ def _dispatch_result(self, dt):
self._is_finished = True
self._result = data
if self.on_success:
- self.on_success(self, data)
+ func = self.on_success()
+ if func:
+ func(self, data)
elif result == 'error':
self._is_finished = True
self._error = data
if self.on_error:
- self.on_error(self, data)
+ func = self.on_error()
+ if func:
+ func(self, data)
elif result == 'progress':
if self.on_progress:
- self.on_progress(self, data[0], data[1])
+ func = self.on_progress()
+ if func:
+ func(self, data[0], data[1])
else:
assert(0)
@@ -336,6 +359,11 @@ def wait(self, delay=0.5):
wait for the request to be finished (until :data:`resp_status` is not
None)
+ .. note::
+ This method is intended to be used in the main thread, and the
+ callback will be dispatched from the same thread as the thread
+ you're calling it.
+
.. versionadded:: 1.1.0
'''
while self.resp_status is None:
@@ -0,0 +1,55 @@
+'''
+UrlRequest tests
+================
+'''
+
+import unittest
+import thread
+from kivy.network.urlrequest import UrlRequest
+from time import sleep
+from kivy.clock import Clock
+
+class UrlRequestTest(unittest.TestCase):
+
+ def _on_success(self, req, *args):
+ self.queue.append((thread.get_ident(), 'success', args))
+
+ def _on_error(self, req, *args):
+ self.queue.append((thread.get_ident(), 'error', args))
+
+ def _on_progress(self, req, *args):
+ self.queue.append((thread.get_ident(), 'progress', args))
+
+
+ def test_callbacks(self):
+ self.queue = []
+ req = UrlRequest('http://google.com',
+ on_success=self._on_success,
+ on_progress=self._on_progress,
+ on_error=self._on_error)
+
+ # don't use wait, but maximum 10s timeout
+ for i in xrange(50):
+ Clock.tick()
+ sleep(.5)
+ if req.is_finished:
+ break
+
+ self.assertTrue(req.is_finished)
+
+ # we should have 2 progress minimum and one success
+ self.assertGreaterEqual(len(self.queue), 3)
+ print self.queue
+
+ # ensure the callback is called from this thread (main).
+ tid = thread.get_ident()
+ self.assertEqual(self.queue[0][0], tid)
+ self.assertEqual(self.queue[-2][0], tid)
+ self.assertEqual(self.queue[-1][0], tid)
+
+ self.assertEqual(self.queue[0][1], 'progress')
+ self.assertEqual(self.queue[-2][1], 'progress')
+ self.assertEqual(self.queue[-1][1], 'success')
+
+ self.assertEqual(self.queue[0][2][0], 0)
+ self.assertEqual(self.queue[-2][2][0], self.queue[-2][2][1])

0 comments on commit b07f637

Please sign in to comment.