Easiest way to insert python "globals" (non-models) into current session/document (e.g., for multiple users on the same server)

I am attempting to create a simple bokeh-server app which can be accessed by multiple users at once. My understanding is that each user is automatically allocated a separate “Document” such that the values of their models (sliders, plots, etc) is independent of other users. However, in order to use certain python callbacks (e.g., a button callback which cannot take inputs), I had to create some python globals to expose certain variables to those callback functions.

Unfortunately, it appears that those python globals in the app file are not independent for multiple users accessing the same bokeh server — I tested this with two separate ssh logins to one bokeh server, and indeed, while they seemed mostly separate, when both sessions attempted to pull/modify Models which rely on combination of those globals, they got confused/overwritten with one another, leading to nonsense.

I saw some advice that basically said “the document is the thing each user gets separately, so just attach any needed session variables to the document.” This makes sense to me in principle, but when I look at how things are added to the root of the curdoc, it appears it is looking specifically for Model objects.

Does anyone have any advice for how to add regular python variables to the current session/document such that when multiple users attach to one bokeh server, those variables are unique to their session?

It appears as though one option might be to create a custom Model object to add to root. This seems nontrivial because the information I am trying to store/pass around is stored in objects (e.g., an astropy WCS or SkyCoord object). Otherwise I could I suppose instantiate a PlainText model as a dummy to store info (though it would need to be added to root but never displayed anywhere).

Grateful for any advice!

As a note, I’ve found curdoc().template_variables but this appears to be for extending jinja templates… if it does attach variables to a document instance, though, would this be a workaround?

Unfortunately, it appears that those python globals in the app file are not independent for multiple users accessing the same bokeh server

Were these “globals” in another module? Then, yes, Python’s own module-caching will ensure there is only one copy of that module, and hence one instance of anything in that module.

I saw some advice that basically said “the document is the thing each user gets separately, so just attach any needed session variables to the document.” This makes sense to me in principle, but when I look at how things are added to the root of the curdoc, it appears it is looking specifically for Model objects.

The advice is not to add it as a “root” but merely attach any ad-hoc attributes that are convenient for your case:

curdoc().my_stuff = 10

That said, the terminology “globals” here seems very ambiguous. Python itself has no globals, the closest thing are “module scope” variables, and then even so, I would expect a “global” to be accessible, well… globally. You see to want the opposite of that. It’s not really clear what exactly you are trying to achieve. It might help if you can back up and explain your requirements more fundamentally, without pre-supposing details about an implementation.

Thanks! I believe assigning attributes to curdoc is exactly what I need.

And just to clarify, what I mean is I have some variable:

wcs = WCS(…)

and then inside a callback, I need to access it

def callback():
global wcs
wcs.do_stuff() ← stuff involves assignment, hence global call needed

So no, these variables are not in another module, I simply meant they were in the namespace of the overall app, and needed to be accessed inside the callback functions.

The problem of course is that two people logged into the same bokeh server need different instances of wcs (among other variables), and the tools in the app that allow one to change/modify wcs were doing so for all users in unexpected and undesirable ways.

Again, it seems from what I’ve read that simply assigning these variables to the curdoc as attributes should mean everyone is operating on different instances, and can change/modify those attributes at will without any other user on the same server having those values change between interactions.

A literal example of what was happening: user uses a selectbox to select coordinates on an image. then clicks button to retrieve an image from the web of those coordinates. however, coordinates are a module level variable in the app. Someone else uses the select box in between when person 1 selects the box and presses the button. Now person 1 clicks the button, but the coordinates sent are those selected by person 2. Or, an attempt to open a link in browser through ssh host by person two actually opens on the computer of person 1. This weird interaction between the behavior when multiple users were accessing the same bokeh server seemed linked to the fact that some module variables were being changed by multiple people, as the module variables are not part of the document that is separate for each user. (At least, that was my working assumption based on browsing the forums).

I will try taking those variables and changing them into document attributes. But if I’ve misunderstood something about how a single bokeh server parses multiple users out, let me know. – Cheers

A toy Minimal Reproducible Example would really help to focus the discussion. I’m having a very hard time mapping what you’ve described occurring back to actual code that might explain it, using only my imagination as a guide.

As for general comments: every time a new user session is opened, the app code gets executed inside a new Module namespace of its own for that session. Python caches module imports, so any modules that are imported by the app code will only be imported and executed once, as per standard Python behavior, since all the app code executions occur in the same process space. Bokeh models should all be created de-novo by the app code (Bokeh models should not be shared or re-used in any way between sessions and trying to do so should raise an error).

All of this is assuming a single bokeh serve process. If you are running multiple servers behind a load balancer or using --num-procs there are some further details, since it is possible for the initial HTTP request and subsequent websocket upgrade to end up on different processes. But those details should only matter during the initial load, and not e.g. during user interactions after the load.

Also:

wcs = WCS(…)

def callback():
    global wcs
    wcs.do_stuff() ← stuff involves assignment, hence global call needed

BTW there is no need for global in a case like this, global is only needed if you want to assign a new value to the module-level name.

Using just the code above as a guide: every session should get its own copy of WCS. However, what does WCS do? Does this object squirrel away some data in a module (so there would only be one copy due to cached module imports) or is it backed by a database or some external storage that could be in contention? It’s not really possible to say anything more without more details.

Yes, wcs is having a new value assigned in several of these callbacks (that’s why I wrote “stuff involves assignment”)

I’m definitely puzzled by your statement that in this example, every session should have its own copy of wcs. That is definitely not the case in our experimentation – in this case, the few non-model objects that need to be assigned/accessed throughout the app are all just astronomical objects of various kinds. We do interface with a database, but only to choose what to load.

I will try to construct a minimal toy example, as you suggested. A bit of a challenge, the app was working fine for one user and this came up when a second user joined, so it’s not 100% clear where exactly the issue arose within the full app context. The core functionality of the app involves, e.g., several astronomical packages. But I’ll see what I can do. I wasn’t doing anything weird with async or unlocking threads, etc. But if the basic behavior should be that a simple variable defined and modified throughout an app should not see any crosstalk between users… that was definitely happening. I’ll update if I can get that working; for now I have just distributed the app and told everyone to run their own server.

Right, if you care to dig into the details, you can see where the code is exec’ed every time into a new module namespace, by a CodeRunner (which is ultimately where app code ends up):

Just based on the information here, I have to conclude that WCS somehow has global state that is not safe to access asynchronously (by Bokeh server callbacks), but again an MRE is really what is needed to figure out for sure.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.