Skip to content

Commit

Permalink
Support MESOP_CONCURRENT_UPDATES_ENABLED env var (#868)
Browse files Browse the repository at this point in the history
  • Loading branch information
wwwillchen authored Sep 18, 2024
1 parent 483a2f8 commit a39bc6d
Show file tree
Hide file tree
Showing 12 changed files with 112 additions and 10 deletions.
16 changes: 12 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,31 +41,39 @@ jobs:
# NOTE: bazel test //... doesn't work (due to node_modules)
run: bazel test //mesop/...
- name: Run playwright test (prod mode)
run: yarn playwright test
run: PLAYWRIGHT_HTML_OUTPUT_DIR=playwright-report-prod-mode yarn playwright test
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
if: always()
with:
name: playwright-report-prod-mode
path: playwright-report-prod-mode/
retention-days: 30
- name: Run playwright test (debug/editor mode)
run: MESOP_DEBUG_MODE=true yarn playwright test
run: MESOP_DEBUG_MODE=true PLAYWRIGHT_HTML_OUTPUT_DIR=playwright-report-debug-mode yarn playwright test
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
if: always()
with:
name: playwright-report-debug-mode
path: playwright-report-debug-mode/
retention-days: 30
- name: Run playwright test (concurrency)
run: yarn playwright test mesop/tests/e2e/concurrency/state_test.ts --repeat-each=48 --workers=16
run: PLAYWRIGHT_HTML_OUTPUT_DIR=playwright-report-concurrency yarn playwright test mesop/tests/e2e/concurrency/state_test.ts --repeat-each=48 --workers=16
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
if: always()
with:
name: playwright-report-concurrency
path: playwright-report-concurrency/
retention-days: 30
- name: Run playwright test with concurrent updates enabled
run: MESOP_CONCURRENT_UPDATES_ENABLED=true PLAYWRIGHT_HTML_OUTPUT_DIR=playwright-report-with-concurrent-updates-enabled yarn playwright test
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
if: always()
with:
name: playwright-report-with-concurrent-updates-enabled
path: playwright-report-with-concurrent-updates-enabled/
retention-days: 30
- name: Run playwright test with memory state session
run: MESOP_STATE_SESSION_BACKEND=memory yarn playwright test
run: MESOP_STATE_SESSION_BACKEND=memory PLAYWRIGHT_HTML_OUTPUT_DIR=playwright-report-with-memory-state-session yarn playwright test
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
if: always()
with:
Expand Down
6 changes: 6 additions & 0 deletions docs/api/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ Mesop is configured at the application level using environment variables.

## Configuration values

### MESOP_CONCURRENT_UPDATES_ENABLED

Allows concurrent updates to state in the same session. If this is not updated, then updates are queued and processed sequentially.

By default, this is not enabled. You can enable this by setting it to `true`.

### MESOP_STATE_SESSION_BACKEND

Sets the backend to use for caching state data server-side. This makes it so state does
Expand Down
1 change: 1 addition & 0 deletions mesop/examples/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from mesop.examples import checkbox_and_radio as checkbox_and_radio
from mesop.examples import composite as composite
from mesop.examples import concurrency_state as concurrency_state
from mesop.examples import concurrent_updates as concurrent_updates
from mesop.examples import custom_font as custom_font
from mesop.examples import dict_state as dict_state
from mesop.examples import docs as docs
Expand Down
30 changes: 30 additions & 0 deletions mesop/examples/concurrent_updates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import time

import mesop as me


@me.page(path="/concurrent_updates")
def page():
me.text("concurrent_updates")
me.button(label="Slow state update", on_click=slow_state_update)
me.button(label="Fast state update", on_click=fast_state_update)
me.text("Slow state: " + str(me.state(State).slow_state))
me.text("Fast state: " + str(me.state(State).fast_state))


@me.stateclass
class State:
slow_state: int = 0
fast_state: int = 0


def slow_state_update(e: me.ClickEvent):
me.state(State).slow_state += 1
yield
time.sleep(3)
me.state(State).slow_state += 1
yield


def fast_state_update(e: me.ClickEvent):
me.state(State).fast_state += 1
1 change: 1 addition & 0 deletions mesop/protos/ui.proto
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ message RenderEvent {

message ExperimentSettings {
optional bool experimental_editor_toolbar_enabled = 1;
optional bool concurrent_updates_enabled = 2;
}

// UI response event for updating state.
Expand Down
8 changes: 8 additions & 0 deletions mesop/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,17 @@
"MESOP_AI_SERVICE_BASE_URL", "http://localhost:43234"
)

MESOP_CONCURRENT_UPDATES_ENABLED = (
os.environ.get("MESOP_CONCURRENT_UPDATES_ENABLED", "false").lower() == "true"
)

EXPERIMENTAL_EDITOR_TOOLBAR_ENABLED = (
os.environ.get("MESOP_EXPERIMENTAL_EDITOR_TOOLBAR", "false").lower() == "true"
)

if MESOP_CONCURRENT_UPDATES_ENABLED:
print("Experiment enabled: MESOP_CONCURRENT_UPDATES_ENABLED")

if EXPERIMENTAL_EDITOR_TOOLBAR_ENABLED:
print("Experiment enabled: EXPERIMENTAL_EDITOR_TOOLBAR_ENABLED")

Expand Down Expand Up @@ -95,6 +102,7 @@ def render_loop(
for js_module in js_modules
],
experiment_settings=pb.ExperimentSettings(
concurrent_updates_enabled=MESOP_CONCURRENT_UPDATES_ENABLED,
experimental_editor_toolbar_enabled=EXPERIMENTAL_EDITOR_TOOLBAR_ENABLED,
)
if init_request
Expand Down
13 changes: 13 additions & 0 deletions mesop/tests/e2e/concurrent_updates_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {testInConcurrentUpdatesEnabledOnly} from './e2e_helpers';
import {expect} from '@playwright/test';

testInConcurrentUpdatesEnabledOnly('concurrent updates', async ({page}) => {
await page.goto('/concurrent_updates');
await page.getByRole('button', {name: 'Slow state update'}).click();
await page.getByRole('button', {name: 'Fast state update'}).click();
await expect(page.getByText('Fast state: 1')).toBeVisible();
await expect(page.getByText('Slow state: 1')).toBeVisible();

// Slow state will update after the next render loop.
await expect(page.getByText('Slow state: 2')).toBeVisible();
});
12 changes: 11 additions & 1 deletion mesop/tests/e2e/e2e_helpers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import {test as base} from '@playwright/test';

export const testInProdOnly = base.extend({
// Skip all tests in this file if MESOP_DEBUG_MODE is 'true'
// Skip this test if MESOP_DEBUG_MODE is 'true'
page: async ({page}, use) => {
if (process.env.MESOP_DEBUG_MODE === 'true') {
base.skip(true, 'Skipping test in debug mode');
}
await use(page);
},
});

export const testInConcurrentUpdatesEnabledOnly = base.extend({
// Skip this test if MESOP_CONCURRENT_UPDATES_ENABLED is not 'true'
page: async ({page}, use) => {
if (process.env.MESOP_CONCURRENT_UPDATES_ENABLED !== 'true') {
base.skip(true, 'Skipping test in concurrent updates disabled mode');
}
await use(page);
},
});
29 changes: 25 additions & 4 deletions mesop/web/src/services/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,12 @@ export class Channel {
} else {
this.rootComponent = rootComponent;
}

const experimentSettings = uiResponse
.getRender()!
.getExperimentSettings();
if (experimentSettings) {
this.experimentService.concurrentUpdatesEnabled =
experimentSettings.getConcurrentUpdatesEnabled() ?? false;
this.experimentService.experimentalEditorToolbarEnabled =
experimentSettings.getExperimentalEditorToolbarEnabled() ??
false;
Expand Down Expand Up @@ -280,12 +281,32 @@ export class Channel {
this.init(this.initParams, request);
};
this.logger.log({type: 'UserEventLog', userEvent: userEvent});

if (this.status === ChannelStatus.CLOSED) {
initUserEvent();
} else {
this.queuedEvents.push(() => {
initUserEvent();
});
this.queuedEvents.push(initUserEvent);
if (this.experimentService.concurrentUpdatesEnabled) {
// We will wait 1 second to see if the server will respond with a new state.
// This addresses common use cases where a user may
// type in a text input and then click a button and
// they would expect the updated text input state to be
// included in the click button event.
setTimeout(() => {
const initUserEventIndex = this.queuedEvents.findIndex(
(event) => event === initUserEvent,
);
// The initUserEvent may have already been removed off the queue
// if the response came back from the server already.
if (initUserEventIndex !== -1) {
const initUserEvent = this.queuedEvents.splice(
initUserEventIndex,
1,
)[0];
initUserEvent();
}
}, 1000);
}
}
}

Expand Down
1 change: 1 addition & 0 deletions mesop/web/src/services/experiment_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ import {Injectable} from '@angular/core';
providedIn: 'root',
})
export class ExperimentService {
concurrentUpdatesEnabled = false;
experimentalEditorToolbarEnabled = false;
}
4 changes: 3 additions & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ export default defineConfig({

/* Run your local server before starting the tests */
webServer: {
command: `MESOP_STATE_SESSION_BACKEND=${
command: `MESOP_CONCURRENT_UPDATES_ENABLED=${
process.env.MESOP_CONCURRENT_UPDATES_ENABLED || 'false'
} MESOP_STATE_SESSION_BACKEND=${
process.env.MESOP_STATE_SESSION_BACKEND || 'none'
} bazel run //mesop/cli -- --path=mesop/mesop/example_index.py --prod=${
process.env.MESOP_DEBUG_MODE === 'true' ? 'false' : 'true'
Expand Down
1 change: 1 addition & 0 deletions scripts/cli.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Uses editor_cli which provides a faster development cycle than the regular "cli" target.
(lsof -t -i:32123 | xargs kill) || true && \
MESOP_CONCURRENT_UPDATES_ENABLED=true \
MESOP_EXPERIMENTAL_EDITOR_TOOLBAR=true \
ibazel run //mesop/cli:editor_cli -- --path="mesop/mesop/example_index.py" --reload_demo_modules

0 comments on commit a39bc6d

Please sign in to comment.