diff --git a/frontend_tests/node_tests/lib/events.js b/frontend_tests/node_tests/lib/events.js index d029372d73..e09b5679df 100644 --- a/frontend_tests/node_tests/lib/events.js +++ b/frontend_tests/node_tests/lib/events.js @@ -517,6 +517,8 @@ exports.fixtures = { restart: { type: "restart", + zulip_version: "4.0-dev+git", + zulip_feature_level: 55, server_generation: 2, immediate: true, }, diff --git a/templates/zerver/api/changelog.md b/templates/zerver/api/changelog.md index 75fdff5b02..26d490e0a9 100644 --- a/templates/zerver/api/changelog.md +++ b/templates/zerver/api/changelog.md @@ -10,6 +10,11 @@ below features are supported. ## Changes in Zulip 4.0 +**Feature level 59** + +* [`GET /events`](/api/get-events): Added new `zulip_version` and + `zulip_feature_level` fields to the `restart` event. + **Feature level 58** * [`POST /register`](/api/register-queue): Added the new diff --git a/zerver/lib/event_schema.py b/zerver/lib/event_schema.py index d9eb8ea6e4..c83a2a91ee 100644 --- a/zerver/lib/event_schema.py +++ b/zerver/lib/event_schema.py @@ -1142,7 +1142,13 @@ def check_realm_user_update( restart_event = event_dict_type( - required_keys=[("type", Equals("restart")), ("server_generation", int), ("immediate", bool)] + required_keys=[ + ("type", Equals("restart")), + ("zulip_version", str), + ("zulip_feature_level", int), + ("server_generation", int), + ("immediate", bool), + ] ) check_restart_event = make_checker(restart_event) diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 761df9dbca..9e92fbc966 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -3114,6 +3114,68 @@ paths: "realm_id": 2, "id": 0, } + - type: object + description: | + Event sent to all the users whenever the Zulip server restarts. + + Specifically, this event is sent whenever the Tornado process + for the user is restarted; in particular, this will always happen + when the Zulip server is upgraded. + + Clients can use this event to know when they should get a new + event queue after a server upgrade. Clients doing so must implement + a random delay strategy to spread such restarts over 10 minutes or + more to avoid creating a synchronized thundering herd effect. + properties: + id: + $ref: "#/components/schemas/EventIdSchema" + type: + allOf: + - $ref: "#/components/schemas/EventTypeSchema" + - enum: + - restart + zulip_version: + type: string + description: | + The Zulip version number, in the format where this appears + in the [server_settings](/api/get-server-settings) and + [register](/api/register-queue) responses. + + **Changes**: New in Zulip 4.0 (feature level 59). + zulip_feature_level: + type: integer + description: | + The [Zulip feature level](/api/changelog) of the server + after the restart. + + Clients can safely avoid refetching their state and + creating a new event queue when the API feature level has not + changed, or when they know the specific feature level change + is not relevant to the client (E.g. it just adds a new endpoint + that the client doesn't use). + + **Changes**: New in Zulip 4.0 (feature level 59). + immediate: + type: boolean + description: | + Whether the client should fetch a new event queue immediately, + rather than using a backoff strategy to avoid thundering herds. + A Zulip development server uses this parameter to reload + clients immediately. + server_generation: + type: integer + description: | + The timestamp at which the server started. + additionalProperties: false + example: + { + "id": 0, + "immediate": True, + "server_generation": 1619334181, + "type": "restart", + "zulip_feature_level": 57, + "zulip_version": "4.0-dev+git", + } - type: object additionalProperties: false description: | diff --git a/zerver/tests/test_event_system.py b/zerver/tests/test_event_system.py index 0578d30f0f..0f0e44f7ff 100644 --- a/zerver/tests/test_event_system.py +++ b/zerver/tests/test_event_system.py @@ -6,6 +6,7 @@ import orjson from django.conf import settings from django.http import HttpRequest, HttpResponse +from version import API_FEATURE_LEVEL, ZULIP_VERSION from zerver.lib.actions import check_send_message, do_change_user_role, do_set_realm_property from zerver.lib.event_schema import check_restart_event from zerver.lib.events import fetch_initial_state_data, get_raw_user_data @@ -877,6 +878,8 @@ class RestartEventsTest(ZulipTestCase): restart_event, dict( type="restart", + zulip_version=ZULIP_VERSION, + zulip_feature_level=API_FEATURE_LEVEL, server_generation=settings.SERVER_GENERATION, immediate=True, id=0, diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index c89a4cd7e0..e33daf5e67 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -161,7 +161,12 @@ from zerver.lib.event_schema import ( check_user_group_update, check_user_status, ) -from zerver.lib.events import apply_events, fetch_initial_state_data, post_process_state +from zerver.lib.events import ( + RestartEventException, + apply_events, + fetch_initial_state_data, + post_process_state, +) from zerver.lib.markdown import MentionData from zerver.lib.message import render_markdown from zerver.lib.test_classes import ZulipTestCase @@ -198,6 +203,7 @@ from zerver.openapi.openapi import validate_against_openapi_schema from zerver.tornado.event_queue import ( allocate_client_descriptor, clear_client_event_queues_for_testing, + send_restart_events, ) from zerver.views.realm_playgrounds import access_playground_by_id @@ -1938,6 +1944,10 @@ class NormalActionsTest(BaseAction): events = self.verify_action(lambda: do_set_zoom_token(self.user_profile, None)) check_has_zoom_token("events[0]", events[0], value=False) + def test_restart_event(self) -> None: + with self.assertRaises(RestartEventException): + self.verify_action(lambda: send_restart_events(immediate=True)) + class RealmPropertyActionTest(BaseAction): def do_set_realm_property_test(self, name: str) -> None: diff --git a/zerver/tornado/event_queue.py b/zerver/tornado/event_queue.py index 555e20e0b6..692ea9b93c 100644 --- a/zerver/tornado/event_queue.py +++ b/zerver/tornado/event_queue.py @@ -33,6 +33,7 @@ from django.conf import settings from django.utils.translation import gettext as _ from typing_extensions import TypedDict +from version import API_FEATURE_LEVEL, ZULIP_VERSION from zerver.decorator import cachify from zerver.lib.message import MessageDict from zerver.lib.narrow import build_narrow_filter @@ -572,7 +573,12 @@ def load_event_queues(port: int) -> None: def send_restart_events(immediate: bool = False) -> None: - event: Dict[str, Any] = dict(type="restart", server_generation=settings.SERVER_GENERATION) + event: Dict[str, Any] = dict( + type="restart", + zulip_version=ZULIP_VERSION, + zulip_feature_level=API_FEATURE_LEVEL, + server_generation=settings.SERVER_GENERATION, + ) if immediate: event["immediate"] = True for client in clients.values():