Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

threading a bottle app

I have a simple bottle script that forwards button processes on a web page. Within the same script I was looking to have a continuous loop that among other tasks listened out for these button presses. I attempted to run the bottle script in a separate thread but it doesn't work as I expected.

Is there a better (or should I say correct) way to do this?

from bottle import get, post, request, run, redirect
import threading

@get('/button')
def button():
    return '''
        <form action="/button" method="post">
            <input type="submit" value="Push" />
        </form>
    '''

@post('/button')
def action():
    print "button pushed"
    pushed = True
    redirect("/button")

#run(host='localhost', port=8080)
threading.Thread(target=run, kwargs=dict(host='localhost', port=8080)).start()


def method():
    pushed = False
    print "started"
    while 1:
        # do other stuff
        if pushed:
            print "push recieved"
            pushed = False

method()
like image 959
user2921789 Avatar asked Oct 26 '13 08:10

user2921789


1 Answers

It doesn't work because it's only seeing the local variable pushed defined inside of method as opposed to a globally visible and modifiable pushed variable.

What you need instead is the following (but scroll down for a correct solution):

pushed = False

@post('/button')
def action():
    global pushed  # needed because otherwise assigning to `pushed` will
                   # just create a local variable shadowing the global one
    pushed = True
    redirect("/button")

def method():
    # pushed = False   <----- this line is not needed, and if enabled, will, again, 
    #                         create a local variable shadowing the global one
    global pushed  # again, otherwise the `pushed = False` statement will create a local variable
    while True:  # use real booleans, i.e. True/False not 1/0
        if pushed:
            print "push recieved"
            pushed = False

method()

NOTE: pay attention to the comments I added inside of the snippet.

BUT, it is bad practice to communicate with threads via global variables (or normal variables) because multiple other threads might be accessing (reading or writing) the same variable simultaneously. Instead, to signal events between threads, use queues:

from Queue import Queue, Empty

button_pressed = Queue()

@post('/button')
def action():
    button_pressed.put(1)  # can be any value really
    redirect("/button")

def method():
    while True:
        try:
            button_pressed.get_nowait()
        except Empty:
            # NOTE: if you don't do anything here, this thread
            # will consume a single CPU core
            pass
        else:
            print "push recieved"

get_nowait() checks if something has been put into the queue and if yes, returns it; otherwise it immediately raises Empty. You can also pass a non-zero timeout to it, or call it without a timeout, in which case it will wait until something is available in the queue.

It's likely better to use .get() with a timeout rather than the nowait version to not make the thread consume CPU uselessly.

Furthermore, code such as the line where you start the thread as well as where you call method() should not be put directly into the module top-level scope; instead, call them conditionally like this:

if __name__ == '__main__':
    threading.Thread(target=run, kwargs=dict(host='localhost', port=8080)).start()
    method()

this way that code will only execute if the .py file is executed directly and not when it's imported as a module. Also, consider calling run() normally and instead putting method inside of a thread.

like image 89
Erik Kaplun Avatar answered Sep 30 '22 13:09

Erik Kaplun