-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
[BUG] dcc.interval missed triggering callback on time #2186
Comments
Found a better place to file this issue @ https://github.com/plotly/dash-core-components, closing this issue for now. |
The interval components fires the callback on the js side at the specified interval, but if your callbacks themselves takes longer than the interval time, the time will not be synced with the front end. Here you have I suggest using the interval component with an interval more than the execution time of your callbacks. |
Hi @T4rk1n, Good explanation and I'm aware the Dash is employing JS for the timer. In case you are not aware, I'm specializing in building time sensitive python program and I doubt the behavior described above is working as intended. Firstly, I've tried removing the inspect, the result is similar to the earlier. The question I have in mind is that did you even try to eliminate the issue by removing inspect as that seems to be quick one before you posted your comment above? Next I've tried with the following to prove your hypothesis where inspect is taking more than 1 second: import dash
import datetime
import inspect
from dash.dependencies import Input, Output
from dash import html
from dash import dcc
from time import time as timer
app = dash.Dash(__name__)
app.layout = html.Div([
dcc.Interval(id='my_interval', disabled=False, n_intervals=0, interval=1000 * 1),
html.Div("Counter: ", style={"display": "inline-block"}),
html.Div(children=None, id="cnt_val", style={"display": "inline-block", "margin-left": "15px"}),
])
def get_time_diff(start):
prog_time_diff = timer() - start
hours, rem = divmod(prog_time_diff, 3600)
minutes, seconds = divmod(rem, 60)
minutes = int(minutes)
return hours, minutes, seconds
def get_ms_time_diff(start):
_, minutes, seconds = get_time_diff(start)
fraction_of_seconds = seconds - int(seconds)
seconds = int(seconds)
milliseconds = fraction_of_seconds * 1000
milliseconds = int(milliseconds)
return minutes, seconds, milliseconds
def print_timestamp_checkpoint(comment="Checkpoint timestamp", start=None):
timestamp_str = datetime.datetime.now().isoformat().replace("T", " ")
if start:
minutes, seconds, milliseconds = get_ms_time_diff(start)
print("{}, {}, Delta: {}m:{}s.{}ms".format(
comment, timestamp_str, minutes, seconds, milliseconds,
))
else:
print("{}, {}".format(
comment, timestamp_str,
))
@app.callback(
Output('cnt_val', 'children'),
[Input('my_interval', 'n_intervals')]
)
def update(n_intervals):
start = timer()
timer_label__children = ""
print("{} Line: {}: {}".format(
inspect.getframeinfo(inspect.currentframe()).function,
inspect.getframeinfo(inspect.currentframe()).lineno,
datetime.datetime.now().isoformat().replace("T", " "),
))
print_timestamp_checkpoint(comment="Checkpoint", start=start)
return timer_label__children
app.run_server(debug=True) and below is the screenshot:
It clearly indicated where the callback itself including inspect does not consume more than 300ms and that's only limited for the first callback. All subsequent callback consumes negligible time below millisecond range. I totally understand Dash has been architected in a way where JS script is driving the callback to support Python, R, Julia, and F# (experimental) on top and it has my full support on this move. However, when user of any of these programming languages encounters functionality issue such as the one described above, you can't just blame the architecture itself and then declare it as problem of others. Even if JS fires the callback precisely at 1 second but for some reason the callback is not getting the firing, that defeat the whole purpose of dcc.interval as the fundamental functionality declared in the user guide documentation do not match with the actual code behavior. That's why I'm filing this case as an issue to be addressed. Btw, I'm surprised to learn that no user from Dash Enterprise actually discover this behavior and/or file this case earlier. Please don't get me wrong, I'm not here to debate with you about Dash's architecture or limitation as this is one of the gating issue for my current support and I would like to get issue to be fixed. Unless you intend to fix at your end, I would like to explore how to fix this issue with you using workaround since official fix from Dash is going to be time consuming and I would prefer it to be addressed before that. Let's explore ideas for workaround. From Python point of view, can user somehow replaces dcc.interval with a time.sleep() function? I'm 100% certain this workaround will work flawlessly. Is there any way user could swap dcc.interval with time.sleep() or other custom function? |
This behavior is known, it's the reason why I refactored the long_callback to background_callback with a native request loop. Any interval inferior to a couple seconds will end up with this kind of race condition, in production server you'll have to add network latency to the mix. You could use a |
Thanks. Perhaps you could please share a more practical example so that I don't have to perform my own R&D? |
Using the progress output of background callbacks to update on a loop from the backend: import time
from dash import Dash, html, DiskcacheManager, callback, Input, Output
app = Dash(__name__, background_callback_manager=DiskcacheManager())
app.layout = html.Div([
html.Div(id='output'),
html.Div(id='side'),
html.Button('start', id='start'),
])
@callback(
Output('output', 'children'),
Input('start', 'n_clicks'),
background=True,
progress=Output('side', 'children')
)
def on_click(set_progress, n_clicks):
i = 1
while i < 30:
set_progress(f'Progress {i}')
time.sleep(1)
i += 1
return 'Finished'
if __name__ == '__main__':
app.run(debug=True) |
Thank you @T4rk1n but no quite what I'm looking for. As dcc.interval running on JS is forever loop, I've modified your codes above to be running in forever loop instead of for loop. It is working for the codes above but "Finished" is never been returned and called. Also, if add in more callback functions, this forever loop will not exit and subsequently causing starvation to other callback functions. It seems like I'll have to stick with dcc.interval for the meantime. I have no more issue, you may go ahead and close this case if you wanted to. |
Hi Dash Admin,
Environment
Frontend (P/S: You don't need browser to reproduce the issue below):
Describe the bug
Using the simple Python script below, the intention is to confirm the time for dcc.interval component to trigger callback as specified by the interval parameter. The screenshot below captured what had happened after the script ran for about 30 seconds. The callback was fired neither according to the 1 second interval nor consistent with 1 second interval. In fact, it was fired twice within the gap of 100ms to 300ms. You may find the additional notes within the screenshot dump itself.
Expected behavior
dcc.interval should fire callback precisely at interval of 1 second and consistently doing so. It should NOT fire the callback twice (with gap of 100ms and 300ms), making it clearly it is a defect. I suspect this issue is not newly introduced in Dash 2.x.x as I've seen similar behavior in Dash 1.x.x as well.
I would appreciate if someone from your team will look into this issue as this should be a fundamental functionality of dcc.interval component and it has been missed out by your test coverage since long ago.
Thank you in advance for your attention.
Codes
Screenshots
The text was updated successfully, but these errors were encountered: