1

I am trying to run bottle.py as a process inside a script but I am frustrated because it doesn't work as I expected. Here is a hypothetical/shortened representation of my script. I tried to add as much comments as I could.

magball.py

import serial
from bottle import route, run, template, request
import multiprocessing
import logging
import datetime


#Here are the most important variables which will be used in the entire script
global lastcommand #The last command received from bottle web interface
global status #What is the current status

lastcommand = 'Go Home' #Options: Start Drawing, Go Home, Next Drawing
status = 'home' #Options: home, homing (means going home), drawing

def log_this(level, msg)
    #a special log function based on logging library for debugging
    pass # <- details are not important, it is working fine

now = datetime.datetime.now()
timeString = now.strftime("%Y-%m-%d %H:%M")
info = {'title' : 'MAGBALL', 'time': timeString, 'lastcommand': lastcommand, 'status': status} #These are passed to main.tpl
@route('/', method='GET')
@route('/', method='POST')
def index(): #<- This works fine, passing the info to the template file (main.tpl) in views directory
    global lastcommand #<- Isn't this referring to the global lastcommand declared at the beginning?
    if request.method == 'POST':
        lastcommand = request.forms.get("submit") #<- This works fine and lastcommand changes when the button on the web page is clicked    
        log_this('info', (lastcommand + ' clicked on web interface')) #<- I can see in the logs which button is clicked, so, this should mean 
                                                                      #that lastcommand (supposedly the global one) was changed successfully

    return template('main.tpl', info)

p_bottle = multiprocessing.Process(target=run, kwargs=dict(host='0.0.0.0', port=80, debug='True')) #<- I start bottle as a process because I think I have to
p_bottle.daemon = True

def draw_pattern() #<- This function can easily run for hours which is fine/normal for the purpose
                   #Unless lastcommand is not equal to 'Start Drawing' this should run forever
    global status
    global lastcommand
    status = 'drawing' #<- I think this should also refer to the global lastcommand declared at the beginning
    #Opens a random file from a specific directory, 
    for filename in shuffled_directory:
        #reads the file line by line and feeds it to the serial port
        .
        .
        if lastcommand != 'Start Drawing': #<- This never works! Although I see in the logs that lastcommand was changed by index() function
            log_this('warning', 'Last command changed during line feeding.')
            break

def go_home()
    global status
    status = 'homing' #<- I think this should also refer to the global lastcommand declared at the beginning
    #Send some commands to serial port and after that change the value of status
    .
    .
    status = 'home'

while true: #This is the infinite loop that needs to run forever
    global status #Don't these variables also refer to the global variables declared at the beginning? 
    global lastcommand
    p_bottle.start() #The bottle.py web interface runs successfully
    #The following while, if etc never runs. I think this "while true:" statement never gets notified
    #that the values of lastcommand and/or status has changed
    while lastcommand == 'Start Drawing':   
        if status == 'home':
            draw_pattern()
        if status == 'homing':
            time.sleep(5)
        if status == 'drawing':
            pass
    while lastcommand == 'Next Drawing':
        if status == 'home':
            draw_pattern()
        if status == 'homing':
            time.sleep(5)
        if status == 'drawing':
            go_home()
            draw_pattern()
    while lastcommand == 'Go Home':
        if status == 'home':
            pass
        if status == 'homing':
            pass
        if status == 'drawing':
            go_home()

main.tpl

<!DOCTYPE html>
   <head>
      <title>{{ title }}</title>
   </head>
   <body>
      <h1>Magball Web Interface</h1>
      <h2>The date and time on the server is: {{ time }}</h2>
    <h2>The last command is: {{ lastcommand }}</h2>
    <h2>Status: {{ status }}</h2>
    <form name='magballaction' method='POST'>
    <input type="submit" name="submit" value="Start Drawing">
    <input type="submit" name="submit" value="Go Home">
    <input type="submit" name="submit" value="Next Drawing">
    </form>
   </body>
</html>

And here is what happens:
The bottle process runs fine (the web page is accesible) and when I click the buttons on the web page, I see in the logs that the value of lastcommand was changed by web interface.

However the infinite loop at the end (while true:) never gets notified that the value of lastcommand was changed. So forexample, if the status = home and lastcommand was changed to 'Start Drawing' from the web interface, it does not perform draw_pattern(). It does nothing.

I tried to put everything inside the def index() function before the return template('main.tpl', info) line but then the codes run however the web interface waits for the code to complete in order to start. Since draw_pattern() lasts for hours, the return template('main.tpl', info) line practically does not run and the web page is not accessible.

I also tried to run def index() as a normal function, but when I do so, the only thing that runs is the web inteface (def index()).

So, what can I do to notify the while loop that the value of lastcommand and/or status has changed?

AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173
koray
  • 31
  • 4
  • 1
    By the way I am a complete newbie to Python so probably I am doing something stupid here. Please excuse my ignorance. – koray Jun 09 '17 at 07:12
  • I am sorry if I am missing something here, but do not you need interprocess communication to notify one process about the sate of the other? – SRC Jun 09 '17 at 07:46
  • SRC thank you for your answer. Your are probably right. I just tried to implement interprocess communication through the use of global variables which failed hence my problem and this post. – koray Jun 09 '17 at 08:23
  • Global variables between two processes are not shared. So it is not surprising that this does not work. I kind of believe that multiprocessing will not help you here. As you can collect the output of a process inside the parent process once the child has finished. Being a server process the child will never finish and thus you will not get any values back from it. Either you can use multithreading in this case (where you will probably have shared states of variables and thus more tricky to manage race conditions) or you can use something like [Tornado](http://www.tornadoweb.org/en/stable/) – SRC Jun 09 '17 at 08:53
  • The above is IMHO. It can be slightly wrong. And that is why I did not write a real answer. You can check this [SO thread](https://stackoverflow.com/questions/11055303/python-multiprocessing-global-variable-updates-not-returned-to-parent) to see a similar discussion. And [this](http://sebastianraschka.com/Articles/2014_multiprocessing.html) is a decent intro to multiprocessing. – SRC Jun 09 '17 at 08:54

1 Answers1

2

For anyone who is interested I have solved the problem using threading instead of multiprocessing. So instead of using this:

p_bottle = multiprocessing.Process(target=run, kwargs=dict(host='0.0.0.0', port=80, debug='True')) 
p_bottle.daemon = True

I used:

p_bottle = threading.Thread(target=run, kwargs=dict(host='0.0.0.0', port=80, debug='True'))
p_bottle.daemon = True

and now the function in the thread can change the global variable successfully which is instantly picked up by the infinite while true loop. Thanks everyone for suggestions.

koray
  • 31
  • 4