views:

223

answers:

3

EDIT

I solved the issue by forking the process instead of using threads. From the comments and links in the comments, I don't think threading is the right move here.

Thanks everyone for your assistance.

FINISHED EDIT

I haven't done much with threading before. I've created a few simple example "Hello World" scripts but nothing that actually did any work.

To help me grasp it, I wrote a simple script using the binaries from Nagios to query services like HTTP. This script works although with a timeout of 1 second if I have 10 services that timeout, the script will take over 10 seconds long.

What I want to do is run all checks in parallel to each other. This should reduce the time it takes to complete.

I'm currently getting segfaults but not all the time. Strangely at the point where I check the host in the processCheck function, I can printout all hosts. Just after checking the host though, the hosts variable only prints one or two of the hosts in the set. I have a feeling it's a namespace issue but I'm not sure how to resolve.

I've posted the entire code here sans the MySQL db but a result from he service_list view looks like.

Any assistance is greatly appreciated.

6543L, 'moretesting.com', 'smtp')
(6543L, 'moretesting.com', 'ping')
(6543L, 'moretesting.com', 'http')


from commands import getstatusoutput
import MySQLdb
import threading
import Queue
import time

def checkHost(x, service):
    command = {}
    command['http'] = './plugins/check_http -t 1 -H '
    command['smtp'] = './plugins/check_smtp -t 1 -H '

    cmd = command[service]
    cmd += x
    retval = getstatusoutput(cmd)
    if retval[0] == 0:
        return 0
    else: 
        return retval[1]

def fetchHosts():
    hostList = []
    cur.execute('SELECT veid, hostname, service from service_list')
    for row in cur.fetchall():
        hostList.append(row)
    return hostList

def insertFailure(veid, hostname, service, error):
    query = 'INSERT INTO failures (veid, hostname, service, error) '
    query += "VALUES ('%s', '%s', '%s', '%s')" % (veid, hostname, service, error)
    cur.execute(query)
    cur.execute('COMMIT')


def processCheck(host):
    #If I print the host tuple here I get all hosts/services
    retval = checkHost(host[1], host[2])
    #If I print the host tuple here, I get one host maybe two
    if retval != 0:
        try:
            insertFailure(host[0], host[1], host[2], retval)
        except:
            pass
    else:
        try:
            #if service is back up, remove old failure entry
            query = "DELETE FROM failures WHERE veid='%s' AND service='%s' AND hostname='%s'" % (host[0], host[2], host[1])
            cur.execute(query)
            cur.execute('COMMIT')
        except:
            pass
    return 0

class ThreadClass(threading.Thread):

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

    def run(self):
        processCheck(queue.get())
        time.sleep(1)

def main():
    for host in fetchHosts():
        queue.put(host)
        t = ThreadClass(queue)
        t.setDaemon(True)
        t.start()

if __name__ == '__main__':
    conn = MySQLdb.connect('localhost', 'root', '', 'testing')
    cur = conn.cursor()
    queue = Queue.Queue()
    main()
    conn.close()
+7  A: 

The MySQL DB driver isn't thread safe. You're using the same cursor concurrently from all threads.

Try creating a new connection in each thread, or create a pool of connections that the threads can use (e.g. keep them in a Queue, each thread gets a connection, and puts it pack when it's done).

orip
Thanks. I've changed the code to start a new connection each time though the same issue occurs. Is it possible that the commands module is not thread safe? It appears that that's where the problem lies.
Seth
I would be surprised if the commands module isn't threadsafe, but in any case it's deprecated - prefer the subprocess module.
orip
A: 

You should be constructing and populating your queue first. When the entire queue is constructed and populated, then you should construct a number of threads which then, each in a loop, polls the queue and processes an item on the queue.

Jonathan Feinberg
A: 

You realize Python doesn't do true multi-threading as you would expect on a multi-core processor:

See Here And Here

Don't expect those 10 things to take 1 second each. Besides, even in true multi-threading there is a little overhead associated with the threads. I'd like to add that this isn't a slur against Python.

wheaties
Thanks for the info. Do you think creating a fork for each check would work? As I understand things, each fork should think it's independent. Since it's talking back with MySQL, I wouldn't need to pass any variables back and forth.
Seth
@wheaties - only for Python bytecode. Accessing a DB and running a subprocess release the GIL, and the Python code itself isn't what's slow here, so he should get a linear speedup (e.g. 1 sec total).
orip