Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pn.Param multi-threading race condition #7564

Open
Dsolnik opened this issue Dec 20, 2024 · 4 comments
Open

pn.Param multi-threading race condition #7564

Dsolnik opened this issue Dec 20, 2024 · 4 comments

Comments

@Dsolnik
Copy link

Dsolnik commented Dec 20, 2024

Description of expected behavior and the observed behavior

I would like to have every manual change to a selector change the linked parameter. I would like pn.Param to behave consistently if nthreads=1 or nthreads>1

If you have a multi-threaded app using pn.Param, it can happen that:

  1. A user from the UI changes a selector to A.
  2. Before the UI is finished processing the change, they change their mind and click B.

In this case, it's likely the second change will probably be skipped if the link_widget callback is called before the update changes. because in pn.Param, we skip the update if there is currently an update in progress for that parameter:

if p_name in self._updating:

Complete, minimal, self-contained example code that reproduces the issue

import panel as pn
import param

pn.extension(nthreads=10)

class Picker(param.Parameterized):
    selection = param.Selector(objects=['a', 'b', 'c'], default='a')

    @param.depends('selection', watch=True)
    def on_change(self):
        time.sleep(3)

    def pane(self):
        return pn.Row(self.param.selection.rx())

picker = Picker()

picker.pane()

pn.Param(picker)

Please note that if you set nthreads=1 in the previous example, everything works fine :)

Screenshots or screencasts of the bug in action

Image

  • [ x ] I may be interested in making a pull request to address this
@Dsolnik Dsolnik changed the title pn.Param ignores change if nthreads > 1 pn.Param multi-threading race condition Dec 20, 2024
@pierrotsmnrd
Copy link
Collaborator

pierrotsmnrd commented Jan 10, 2025

@Dsolnik have you tried making def on_change async ? The following works with Panel 1.5.5 :

import time
import panel as pn
import param

pn.extension(nthreads=10)

class Picker(param.Parameterized):
    selection = param.Selector(objects=['a', 'b', 'c'], default='a')

    @param.depends('selection', watch=True)
    async def on_change(self):
        print("BEFORE TIME SLEEP")
        time.sleep(2)
        print("AFTER TIME SLEEP")

    def pane(self):
        return pn.Row(self.param.selection.rx())

picker = Picker()


pn.Column(picker.pane(), pn.Param(picker)).servable()
# panel serve --autoreload script.py
panel.issue.7564.mov

@Dsolnik
Copy link
Author

Dsolnik commented Jan 10, 2025

Thanks for the response! It makes sense that changing the callback to async would allow it to be queued.

I wish I was able to control that. What I'm actually doing is linking together the picker through reactive expressions. @pierrotsmnrd, Do you know if Is there an easy way to make a reactive expression operate like an async watcher here?

import time
import panel as pn
import param

pn.extension(nthreads=10)

class Picker(param.Parameterized):
    selection = param.Selector(objects=['a', 'b', 'c'], default='a')

class View(param.Parameterized):
    selection = param.Selector(objects=['a', 'b', 'c'], default='a', allow_refs=True)

    def pane(self):
        return pn.Row(self.param.selection.rx())

picker = Picker()

def get_data(x):
    # time intensive operation
    time.sleep(2)
    return x
    
view = View(selection=picker.param.selection.rx.pipe(get_data).rx())

pn.Column(view.pane(), pn.Param(picker)).servable()

@pierrotsmnrd
Copy link
Collaborator

Making get_data async seems to work as expected :

async def get_data(x):
    # time intensive operation
    time.sleep(2)
    return x

@philippjfr
Copy link
Member

Thanks for the super detailed writeup here. While async does provide a workaround here (thanks @pierrotsmnrd) this is a real bug. Not sure if it's in Panel or Param yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants