0

I'm using flask to develop my application with multiple routes. Running my application on my local machine with a single "thread", it works very well. When I deploy it the problem occurs. The following snippet represents the situation:

object_a = None

@app.route(/route_a)
def route_a():
   global object_a
   object_a = do_something()
   return render_template("route_a.html")

@app.route(/route_b)
def route_b():
   global object_a
   object_a.get_something
   return render_template("route_b.html")

In this example, I used a global variable that is accessible through the routes functions. With a single worker/thread, this approach worked, but when I use gunicorn with 3 workers, e.g., the application crashes because the object accessed is empty. My main hypothesis is that work shifts during the process. Is there a proper way to handle this behavior?

EDIT 1:

The following command are executed for gunicor:

gunicorn --workers 3 --timeout 180 --bind 
  
   
Juliano Oliveira
  • 868
  • 1
  • 9
  • 29

2 Answers2

1

When you run multiple workers, there will be separate processes And separate processes can't share data directly. You need to use some kind of IPC to share your object, or store it in external storage, e.g. Redis

aramcpp
  • 339
  • 1
  • 8
  • My main concern is, why my actual session change the worker between requests? Is possible for the same user the worker be locked when user are accessing the aplication? – Juliano Oliveira Oct 20 '22 at 14:58
  • Main reason for having workers is to distribute the load between workers. So I don't think there will be an easy way of achieving what you want. What I can suggest is using external storage, or use single worker. – aramcpp Oct 20 '22 at 15:02
0

Like @aramcpp said, you'll need to use IPC (inter-process comm) because I believe gunicron spawns multiple processes. You could use threading module with socket module to do the communication. It looks like one process does a task, and the other grabs the result... Is that correct?

I haven't used sockets in a while so that part is pseudo code, but this is a solution:

import threading
import socket
import queue

class Sender(threading.Thread):

   def __init__(self, term, q):
      self.term = term
      self.q = q
      
      # < start socket connection here >

      super().__init__()

   def run(self):
      while not self.term.is_set():
         item = self.q.get(block=True)

         # < convert item to byte data  here >

         socket.send(byte_data)


class Receiver(threading.Thread):

   def __init__(self, term, q):
      self.term = term
      self.q = q
      
      # < start socket connection here >

      super().__init__()

   def run(self):
      while not self.term.is_set():
         byte_data = socket.recv()

         # < convert bytes to whatever >

         self.q.put(item)


@app.route(/route_a)
def route_a():

   q = queue.Queue()
   term = threading.Event()
   s = Sender(term, q)

   s.start()

   data = do_something()

   q.put(data, block=True)

   term.set()
   
   return render_template("route_a.html")


@app.route(/route_b)
def route_b():

   q = queue.Queue()
   term = threading.Event()

   r = Receiver(term, q)

   r.start()
   
   item = q.get(block=True) # <--- your item here
   

   return render_template("route_b.html")

A lot of this is pseudo code, so you'll have to do some research on sockets but this is my approach. I'm sure there are other good ways to do this - or even simpler approaches.

The while loops aren't needed if it's a single shot communication, but I added them to show that a thread can run continuously to do ongoing communication.

Also, if you go this route, some resources will suggest using asyncio for IPC. I would discourage this, considering threading will never create locks. Async IPC requires more coordination.

Gunicorn documentation states " Gunicorn does not implement any IPC solution for coordinating between worker processes." So there you go. You'll need to do it yourself. Which isn't so bad because IPC is a very important skill to learn.

alvrm
  • 311
  • 2
  • 9