threading.Thread getting stuck on calling httplib2.Http.request

861 Views Asked by At

The first few lines of the script explain the structure and the mechanism.

The problem that I'm facing is that the execution is getting stuck at line 53. Once the Downloader acts on the first request it generates the api correctly however on reaching http_object.request(audioscrobbler_api) it gets stuck.

The script was coded and tested on another system and it yielded the correct result.

I can confirm that the httplib2 package is not broken as it functions properly while methods of that library (including request) are called from other scripts.

What is causing the script to get stuck ?

Script:

#
# Album artwork downloading module for Encore Music Player application.
# Loosely based on the producer-consumer model devised by E W Djikstra.
#
# The Downloader class (implemented as a daemon thread) acts as the consumer
# in the system where it reads requests from the buffer and tries to fetch the
# artwork from ws.audioscrobbler.com (LastFM's web service portal).
#
# Requester class, the producer, is a standard thread class that places the request
# in the buffer when started.
#
# DBusRequester class provides an interface to the script and is made available on
# the session bus of the DBus daemon under the name of 'com.encore.AlbumArtDownloader'
# which enables the core music player to request downloads.
#

import threading, urllib, httplib2, md5, libxml2, os, dbus, dbus.service, signal
from collections import deque
from gi.repository import GObject
from dbus.mainloop.glib import DBusGMainLoop

requests = deque()
mutex    = threading.Lock()
count    = threading.Semaphore(0)

DBusGMainLoop(set_as_default = True)

class Downloader(threading.Thread):

        def __init__(self):
                threading.Thread.__init__(self)

        def run(self):

                while True:
                        print "=> Downloader waiting for requests"
                        count.acquire()  # wait for new request if buffer is empty

                        mutex.acquire()  # enter critical section
                        request = requests.popleft()
                        mutex.release()  # leave critical section

                        (p, q) = request

                        try:
                                print "=> Generating api for %s by %s" % (p,q) 
                                params             = urllib.urlencode({'method': 'album.getinfo', 'api_key': 'XXX', 'artist': p, 'album': q})
                                audioscrobbler_api = "http://ws.audioscrobbler.com/2.0/?%s" % params
                                print "=> Generated URL %s" % (audioscrobbler_api)

                                http_object   = httplib2.Http()
                                print "=> Requesting response"
                                resp, content = http_object.request(audioscrobbler_api)
                                print "=> Received response"

                                if not resp.status == 200:
                                        print "Unable to fetch artwork for %s by %s" % (q, p)
                                        continue                 # proceed to the next item in queue if request fails

                                doc  = libxml2.parseDoc(content)
                                ctxt = doc.xpathNewContext()
                                res  = ctxt.xpathEval("//image[@size='medium']") # grab the element containing the link to a medium sized artwork

                                if len(res) < 1:
                                        continue                 # proceed to the next item in queue if the required image node is not found

                                image_uri = res[0].content           # extract uri from node

                                wget_status = os.system("wget %s -q --tries 3 -O temp" % (image_uri))

                                if not wget_status == 0:
                                        continue                 # proceed to the next item in queue if download fails

                                artwork_name = "%s.png" % (md5.md5("%s + %s" % (p, q)).hexdigest())

                                os.system("convert temp -resize 64x64 %s" % artwork_name)
                        except:
                                pass                         # handle http request error 

class Requester(threading.Thread):

        def __init__(self, request):
                self.request = request
                threading.Thread.__init__(self)

        def run(self):
                mutex.acquire() # enter critical section
                if not self.request in requests:
                        requests.append(self.request)
                        count.release() # signal downloader

                        mutex.release() # leave critical section

class DBusRequester(dbus.service.Object):

        def __init__(self):
                bus_name = dbus.service.BusName('com.encore.AlbumArtDownloader', bus=dbus.SessionBus())
                dbus.service.Object.__init__(self, bus_name, '/com/encore/AlbumArtDownloader')

        @dbus.service.method('com.encore.AlbumArtDownloader')
        def queue_request(self, artist_name, album_name):

                request   = (artist_name, album_name)
                requester = Requester(request)
                requester.start()

def sigint_handler(signum, frame):
        """Exit gracefully on receiving SIGINT."""

        loop.quit()

signal.signal(signal.SIGINT, sigint_handler)

downloader_daemon = Downloader()
downloader_daemon.daemon = True
downloader_daemon.start()

requester_service = DBusRequester()

loop = GObject.MainLoop()
loop.run()

On doing a Ctrl-C

=> Downloader waiting for requests
=> Generating api for paul van dyk by evolution
=> Generated URL http://ws.audioscrobbler.com/2.0/?album=evolution&api_key=XXXXXXXXXXXXXXXXXXXX&method=album.getinfo&artist=paul+van+dyk
=> Requesting response
^C

Thanks !!

2

There are 2 best solutions below

0
On

The problem was caused by Python's Global Interpreter Lock (GIL).

GObject.threads_init()

fixes the problem.

1
On

When your script gets stuck at line 53, can you break the execution using Ctrl + C and show us the traceback python gives?