Skip to content

Commit a39bc6d

Browse files
authored
Support MESOP_CONCURRENT_UPDATES_ENABLED env var (#868)
1 parent 483a2f8 commit a39bc6d

File tree

12 files changed

+112
-10
lines changed

12 files changed

+112
-10
lines changed

.github/workflows/ci.yml

+12-4
Original file line numberDiff line numberDiff line change
@@ -41,31 +41,39 @@ jobs:
4141
# NOTE: bazel test //... doesn't work (due to node_modules)
4242
run: bazel test //mesop/...
4343
- name: Run playwright test (prod mode)
44-
run: yarn playwright test
44+
run: PLAYWRIGHT_HTML_OUTPUT_DIR=playwright-report-prod-mode yarn playwright test
4545
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
4646
if: always()
4747
with:
4848
name: playwright-report-prod-mode
4949
path: playwright-report-prod-mode/
5050
retention-days: 30
5151
- name: Run playwright test (debug/editor mode)
52-
run: MESOP_DEBUG_MODE=true yarn playwright test
52+
run: MESOP_DEBUG_MODE=true PLAYWRIGHT_HTML_OUTPUT_DIR=playwright-report-debug-mode yarn playwright test
5353
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
5454
if: always()
5555
with:
5656
name: playwright-report-debug-mode
5757
path: playwright-report-debug-mode/
5858
retention-days: 30
5959
- name: Run playwright test (concurrency)
60-
run: yarn playwright test mesop/tests/e2e/concurrency/state_test.ts --repeat-each=48 --workers=16
60+
run: PLAYWRIGHT_HTML_OUTPUT_DIR=playwright-report-concurrency yarn playwright test mesop/tests/e2e/concurrency/state_test.ts --repeat-each=48 --workers=16
6161
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
6262
if: always()
6363
with:
6464
name: playwright-report-concurrency
6565
path: playwright-report-concurrency/
6666
retention-days: 30
67+
- name: Run playwright test with concurrent updates enabled
68+
run: MESOP_CONCURRENT_UPDATES_ENABLED=true PLAYWRIGHT_HTML_OUTPUT_DIR=playwright-report-with-concurrent-updates-enabled yarn playwright test
69+
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
70+
if: always()
71+
with:
72+
name: playwright-report-with-concurrent-updates-enabled
73+
path: playwright-report-with-concurrent-updates-enabled/
74+
retention-days: 30
6775
- name: Run playwright test with memory state session
68-
run: MESOP_STATE_SESSION_BACKEND=memory yarn playwright test
76+
run: MESOP_STATE_SESSION_BACKEND=memory PLAYWRIGHT_HTML_OUTPUT_DIR=playwright-report-with-memory-state-session yarn playwright test
6977
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
7078
if: always()
7179
with:

docs/api/config.md

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ Mesop is configured at the application level using environment variables.
66

77
## Configuration values
88

9+
### MESOP_CONCURRENT_UPDATES_ENABLED
10+
11+
Allows concurrent updates to state in the same session. If this is not updated, then updates are queued and processed sequentially.
12+
13+
By default, this is not enabled. You can enable this by setting it to `true`.
14+
915
### MESOP_STATE_SESSION_BACKEND
1016

1117
Sets the backend to use for caching state data server-side. This makes it so state does

mesop/examples/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from mesop.examples import checkbox_and_radio as checkbox_and_radio
1212
from mesop.examples import composite as composite
1313
from mesop.examples import concurrency_state as concurrency_state
14+
from mesop.examples import concurrent_updates as concurrent_updates
1415
from mesop.examples import custom_font as custom_font
1516
from mesop.examples import dict_state as dict_state
1617
from mesop.examples import docs as docs

mesop/examples/concurrent_updates.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import time
2+
3+
import mesop as me
4+
5+
6+
@me.page(path="/concurrent_updates")
7+
def page():
8+
me.text("concurrent_updates")
9+
me.button(label="Slow state update", on_click=slow_state_update)
10+
me.button(label="Fast state update", on_click=fast_state_update)
11+
me.text("Slow state: " + str(me.state(State).slow_state))
12+
me.text("Fast state: " + str(me.state(State).fast_state))
13+
14+
15+
@me.stateclass
16+
class State:
17+
slow_state: int = 0
18+
fast_state: int = 0
19+
20+
21+
def slow_state_update(e: me.ClickEvent):
22+
me.state(State).slow_state += 1
23+
yield
24+
time.sleep(3)
25+
me.state(State).slow_state += 1
26+
yield
27+
28+
29+
def fast_state_update(e: me.ClickEvent):
30+
me.state(State).fast_state += 1

mesop/protos/ui.proto

+1
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ message RenderEvent {
173173

174174
message ExperimentSettings {
175175
optional bool experimental_editor_toolbar_enabled = 1;
176+
optional bool concurrent_updates_enabled = 2;
176177
}
177178

178179
// UI response event for updating state.

mesop/server/server.py

+8
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,17 @@
2626
"MESOP_AI_SERVICE_BASE_URL", "http://localhost:43234"
2727
)
2828

29+
MESOP_CONCURRENT_UPDATES_ENABLED = (
30+
os.environ.get("MESOP_CONCURRENT_UPDATES_ENABLED", "false").lower() == "true"
31+
)
32+
2933
EXPERIMENTAL_EDITOR_TOOLBAR_ENABLED = (
3034
os.environ.get("MESOP_EXPERIMENTAL_EDITOR_TOOLBAR", "false").lower() == "true"
3135
)
3236

37+
if MESOP_CONCURRENT_UPDATES_ENABLED:
38+
print("Experiment enabled: MESOP_CONCURRENT_UPDATES_ENABLED")
39+
3340
if EXPERIMENTAL_EDITOR_TOOLBAR_ENABLED:
3441
print("Experiment enabled: EXPERIMENTAL_EDITOR_TOOLBAR_ENABLED")
3542

@@ -95,6 +102,7 @@ def render_loop(
95102
for js_module in js_modules
96103
],
97104
experiment_settings=pb.ExperimentSettings(
105+
concurrent_updates_enabled=MESOP_CONCURRENT_UPDATES_ENABLED,
98106
experimental_editor_toolbar_enabled=EXPERIMENTAL_EDITOR_TOOLBAR_ENABLED,
99107
)
100108
if init_request
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {testInConcurrentUpdatesEnabledOnly} from './e2e_helpers';
2+
import {expect} from '@playwright/test';
3+
4+
testInConcurrentUpdatesEnabledOnly('concurrent updates', async ({page}) => {
5+
await page.goto('/concurrent_updates');
6+
await page.getByRole('button', {name: 'Slow state update'}).click();
7+
await page.getByRole('button', {name: 'Fast state update'}).click();
8+
await expect(page.getByText('Fast state: 1')).toBeVisible();
9+
await expect(page.getByText('Slow state: 1')).toBeVisible();
10+
11+
// Slow state will update after the next render loop.
12+
await expect(page.getByText('Slow state: 2')).toBeVisible();
13+
});

mesop/tests/e2e/e2e_helpers.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
import {test as base} from '@playwright/test';
22

33
export const testInProdOnly = base.extend({
4-
// Skip all tests in this file if MESOP_DEBUG_MODE is 'true'
4+
// Skip this test if MESOP_DEBUG_MODE is 'true'
55
page: async ({page}, use) => {
66
if (process.env.MESOP_DEBUG_MODE === 'true') {
77
base.skip(true, 'Skipping test in debug mode');
88
}
99
await use(page);
1010
},
1111
});
12+
13+
export const testInConcurrentUpdatesEnabledOnly = base.extend({
14+
// Skip this test if MESOP_CONCURRENT_UPDATES_ENABLED is not 'true'
15+
page: async ({page}, use) => {
16+
if (process.env.MESOP_CONCURRENT_UPDATES_ENABLED !== 'true') {
17+
base.skip(true, 'Skipping test in concurrent updates disabled mode');
18+
}
19+
await use(page);
20+
},
21+
});

mesop/web/src/services/channel.ts

+25-4
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,12 @@ export class Channel {
200200
} else {
201201
this.rootComponent = rootComponent;
202202
}
203-
204203
const experimentSettings = uiResponse
205204
.getRender()!
206205
.getExperimentSettings();
207206
if (experimentSettings) {
207+
this.experimentService.concurrentUpdatesEnabled =
208+
experimentSettings.getConcurrentUpdatesEnabled() ?? false;
208209
this.experimentService.experimentalEditorToolbarEnabled =
209210
experimentSettings.getExperimentalEditorToolbarEnabled() ??
210211
false;
@@ -280,12 +281,32 @@ export class Channel {
280281
this.init(this.initParams, request);
281282
};
282283
this.logger.log({type: 'UserEventLog', userEvent: userEvent});
284+
283285
if (this.status === ChannelStatus.CLOSED) {
284286
initUserEvent();
285287
} else {
286-
this.queuedEvents.push(() => {
287-
initUserEvent();
288-
});
288+
this.queuedEvents.push(initUserEvent);
289+
if (this.experimentService.concurrentUpdatesEnabled) {
290+
// We will wait 1 second to see if the server will respond with a new state.
291+
// This addresses common use cases where a user may
292+
// type in a text input and then click a button and
293+
// they would expect the updated text input state to be
294+
// included in the click button event.
295+
setTimeout(() => {
296+
const initUserEventIndex = this.queuedEvents.findIndex(
297+
(event) => event === initUserEvent,
298+
);
299+
// The initUserEvent may have already been removed off the queue
300+
// if the response came back from the server already.
301+
if (initUserEventIndex !== -1) {
302+
const initUserEvent = this.queuedEvents.splice(
303+
initUserEventIndex,
304+
1,
305+
)[0];
306+
initUserEvent();
307+
}
308+
}, 1000);
309+
}
289310
}
290311
}
291312

mesop/web/src/services/experiment_service.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ import {Injectable} from '@angular/core';
44
providedIn: 'root',
55
})
66
export class ExperimentService {
7+
concurrentUpdatesEnabled = false;
78
experimentalEditorToolbarEnabled = false;
89
}

playwright.config.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ export default defineConfig({
5151

5252
/* Run your local server before starting the tests */
5353
webServer: {
54-
command: `MESOP_STATE_SESSION_BACKEND=${
54+
command: `MESOP_CONCURRENT_UPDATES_ENABLED=${
55+
process.env.MESOP_CONCURRENT_UPDATES_ENABLED || 'false'
56+
} MESOP_STATE_SESSION_BACKEND=${
5557
process.env.MESOP_STATE_SESSION_BACKEND || 'none'
5658
} bazel run //mesop/cli -- --path=mesop/mesop/example_index.py --prod=${
5759
process.env.MESOP_DEBUG_MODE === 'true' ? 'false' : 'true'

scripts/cli.sh

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Uses editor_cli which provides a faster development cycle than the regular "cli" target.
22
(lsof -t -i:32123 | xargs kill) || true && \
3+
MESOP_CONCURRENT_UPDATES_ENABLED=true \
34
MESOP_EXPERIMENTAL_EDITOR_TOOLBAR=true \
45
ibazel run //mesop/cli:editor_cli -- --path="mesop/mesop/example_index.py" --reload_demo_modules

0 commit comments

Comments
 (0)