Skip to content

Conversation

@suhailnadaf509
Copy link
Contributor

@suhailnadaf509 suhailnadaf509 commented Dec 1, 2025

Summary
Fixes a crash that occurred when creating new rooms (Stage, Chat, or BBB) in the video component, which caused the frontend to remain in a perpetual loading state.

Root Cause
Two related issues triggered the failure:

  1. Lazy-loaded channel relationship during room creation caused a sync/async ORM conflict, leading to a crash during AuditLog serialization.
  2. Missing None handling in WebSocket broadcast logic caused AttributeError if room lookup temporarily returned None due to transaction timing.

Fixes Implemented

  • Pre-warm room.channel after creation to avoid lazy-loading inside async flow.

  • Added None checks in:

    • get_room_config_for_user() to safely handle transient lookup failures
    • WebSocket handlers (push_room_info, push_schedule_data) to skip broadcasts when room config is unavailable

Notes
Issue was more visible in production due to async behavior, connection pooling, and concurrency patterns different from local environments.

Related
Closes #1343
Related: #1339
Includes: #1379

Summary by Sourcery

Prevent crashes and loading hangs when creating new video rooms by hardening room creation and WebSocket room lookups.

Bug Fixes:

  • Avoid ORM lazy-loading issues during audit logging by eagerly associating a newly created room with its channel.
  • Prevent AttributeError in WebSocket handlers by safely handling cases where room configuration is temporarily unavailable.

Enhancements:

  • Skip broadcasting room info or schedule data over WebSocket when room configuration cannot be resolved instead of failing the consumer.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 1, 2025

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adds defensive None-handling around room configuration lookups in WebSocket flows and pre-warms the channel relationship on room creation to prevent async ORM lazy-loading crashes during audit logging and broadcasting.

Sequence diagram for room creation and AuditLog with pre-warmed channel

sequenceDiagram
    actor User
    participant Frontend
    participant ApiServer
    participant RoomService
    participant ORM
    participant AuditLogService

    User->>Frontend: Click create room
    Frontend->>ApiServer: POST /rooms with with_channel=true
    ApiServer->>RoomService: create_room(event, data, creator)
    RoomService->>RoomService: _create_room(data, with_channel=True, creator)
    RoomService->>ORM: Room.objects.create(...)
    ORM-->>RoomService: room
    RoomService->>ORM: Channel.objects.create(event_id=room.event_id, room=room)
    ORM-->>RoomService: channel
    RoomService->>RoomService: room.channel = channel
    RoomService->>AuditLogService: AuditLog.objects.create(..., data={"room": room}, ...)
    AuditLogService->>ORM: Serialize room including channel
    ORM-->>AuditLogService: Serialized data (no lazy-load)
    AuditLogService-->>RoomService: AuditLog entry created
    RoomService-->>ApiServer: room details
    ApiServer-->>Frontend: 200 OK room info
    Frontend-->>User: Room created and UI updates
Loading

Sequence diagram for WebSocket room info push with None-safe config lookup

sequenceDiagram
    actor User
    participant Frontend
    participant WebSocketConsumer
    participant RoomService
    participant RoomRepository
    participant PermissionService

    User->>Frontend: Open room page
    Frontend->>WebSocketConsumer: send push_room_info {room: room_id}
    WebSocketConsumer->>RoomService: get_room_config_for_user(room_id, event_id, user)
    RoomService->>RoomRepository: get_room(id=room_id, event_id=event_id)
    alt Room_not_yet_available
        RoomRepository-->>RoomService: None
        RoomService-->>WebSocketConsumer: None
        WebSocketConsumer->>WebSocketConsumer: conf is None, return
        WebSocketConsumer-->>Frontend: No room_info broadcast
    else Room_available
        RoomRepository-->>RoomService: room
        RoomService->>PermissionService: room.event.get_all_permissions(user)
        PermissionService-->>RoomService: permissions
        RoomService->>RoomService: get_room_config(room, permissions[room] | permissions[room.event])
        RoomService-->>WebSocketConsumer: conf
        WebSocketConsumer->>WebSocketConsumer: Check conf[permissions] contains room:view
        WebSocketConsumer-->>Frontend: send_json room_info
    end
Loading

Sequence diagram for WebSocket schedule data push with None-safe config lookup

sequenceDiagram
    actor User
    participant Frontend
    participant WebSocketConsumer
    participant RoomService
    participant RoomRepository
    participant PermissionService

    User->>Frontend: View room schedule
    Frontend->>WebSocketConsumer: send push_schedule_data {room: room_id}
    WebSocketConsumer->>RoomService: get_room_config_for_user(room_id, event_id, user)
    RoomService->>RoomRepository: get_room(id=room_id, event_id=event_id)
    alt Room_not_yet_available
        RoomRepository-->>RoomService: None
        RoomService-->>WebSocketConsumer: None
        WebSocketConsumer->>WebSocketConsumer: config is None, return
        WebSocketConsumer-->>Frontend: No schedule_data broadcast
    else Room_available
        RoomRepository-->>RoomService: room
        RoomService->>PermissionService: room.event.get_all_permissions(user)
        PermissionService-->>RoomService: permissions
        RoomService->>RoomService: get_room_config(room, permissions[room] | permissions[room.event])
        RoomService-->>WebSocketConsumer: config
        WebSocketConsumer->>WebSocketConsumer: Check config[permissions] contains room:view
        WebSocketConsumer-->>Frontend: send_json schedule_data
    end
Loading

File-Level Changes

Change Details Files
Avoid broadcasting room info or schedule data when the room configuration cannot be resolved, preventing AttributeError in async WebSocket handlers.
  • Add a None-check after get_room_config_for_user in push_room_info and return early when configuration is unavailable
  • Add a None-check after get_room_config_for_user in push_schedule_data and return early when configuration is unavailable
  • Preserve existing permission checks and JSON payload structure when configuration is present
app/eventyay/features/live/modules/room.py
Ensure channel relationship is eagerly set on newly created rooms to avoid lazy-loading inside async serialization and make get_room_config_for_user robust to transient room lookup failures.
  • After creating a Channel when with_channel is True, assign it directly to room.channel to pre-warm the relationship before audit logging
  • In get_room_config_for_user, return None when the async room lookup returns no room instead of assuming it always exists
  • Keep existing permission resolution and configuration building logic intact when a room is found
app/eventyay/base/services/event.py

Assessment against linked issues

Issue Objective Addressed Explanation
#1343 Fix the backend bug causing a fatal server error when creating a room in the video component so that room creation succeeds.
#1343 Prevent the frontend from remaining in a continuous loading state after attempting to create a room (by avoiding the crash and handling transient room lookup failures).

Possibly linked issues

  • #(unlisted): They describe the same bug: room creation in the video component crashes with fatal error and infinite loading; PR fixes it.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • Now that get_room_config_for_user can return None, consider updating its type hints (and any associated callers) to reflect the optional return type so this new behavior is explicit to future readers.
  • When skipping broadcasts in push_room_info and push_schedule_data because the room config is None, consider adding lightweight logging so that unexpected missing rooms can be diagnosed without impacting users.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Now that `get_room_config_for_user` can return `None`, consider updating its type hints (and any associated callers) to reflect the optional return type so this new behavior is explicit to future readers.
- When skipping broadcasts in `push_room_info` and `push_schedule_data` because the room config is `None`, consider adding lightweight logging so that unexpected missing rooms can be diagnosed without impacting users.

## Individual Comments

### Comment 1
<location> `app/eventyay/base/services/event.py:363-365` </location>
<code_context>

 async def get_room_config_for_user(room: str, event_id: str, user):
     room = await get_room(id=room, event_id=event_id)
+    if room is None:
+        return None
     permissions = await database_sync_to_async(room.event.get_all_permissions)(user)
</code_context>

<issue_to_address>
**suggestion:** `get_room_config_for_user` now returns `None` in some cases; consider tightening type hints and callers to reflect the optional return.

Since the function can now return `None`, its return type should be updated to `Optional[...]`, and all callers should be checked (beyond `push_room_info` and `push_schedule_data`) to ensure they correctly handle the `None` case. Making this optionality explicit in the signature reduces the risk of future callers assuming a non-`None` config.

Suggested implementation:

```python
from typing import Optional  # if there's already a typing import, extend it instead of adding a new line


async def get_room_config_for_user(room: str, event_id: str, user) -> Optional[dict]:
    room = await get_room(id=room, event_id=event_id)
    if room is None:
        return None

    permissions = await database_sync_to_async(room.event.get_all_permissions)(user)
    return get_room_config(room, permissions[room] | permissions[room.event])

```

1. If `event.py` already has a `from typing import ...` line, extend it instead of adding a second import, e.g.:
   - `from typing import Any, Dict, Optional`
2. Replace `dict` in the return type with the concrete type used by `get_room_config` if it already has a more precise annotation, for example:
   - `-> Optional[RoomConfig]`
   - or `-> Optional[Dict[str, Any]]`
3. Update all call sites of `get_room_config_for_user` (beyond `push_room_info` and `push_schedule_data`) to handle the `None` case explicitly, for example:
   - Early-return if the config is `None`.
   - Or branch logic to only use the config when it is non-`None`.
4. If you use a static type checker (e.g. mypy), run it to confirm all callers now respect the `Optional[...]` return type.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 363 to +365
async def get_room_config_for_user(room: str, event_id: str, user):
room = await get_room(id=room, event_id=event_id)
if room is None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: get_room_config_for_user now returns None in some cases; consider tightening type hints and callers to reflect the optional return.

Since the function can now return None, its return type should be updated to Optional[...], and all callers should be checked (beyond push_room_info and push_schedule_data) to ensure they correctly handle the None case. Making this optionality explicit in the signature reduces the risk of future callers assuming a non-None config.

Suggested implementation:

from typing import Optional  # if there's already a typing import, extend it instead of adding a new line


async def get_room_config_for_user(room: str, event_id: str, user) -> Optional[dict]:
    room = await get_room(id=room, event_id=event_id)
    if room is None:
        return None

    permissions = await database_sync_to_async(room.event.get_all_permissions)(user)
    return get_room_config(room, permissions[room] | permissions[room.event])
  1. If event.py already has a from typing import ... line, extend it instead of adding a second import, e.g.:
    • from typing import Any, Dict, Optional
  2. Replace dict in the return type with the concrete type used by get_room_config if it already has a more precise annotation, for example:
    • -> Optional[RoomConfig]
    • or -> Optional[Dict[str, Any]]
  3. Update all call sites of get_room_config_for_user (beyond push_room_info and push_schedule_data) to handle the None case explicitly, for example:
    • Early-return if the config is None.
    • Or branch logic to only use the config when it is non-None.
  4. If you use a static type checker (e.g. mypy), run it to confirm all callers now respect the Optional[...] return type.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a critical server crash that occurred when creating new video rooms (Stage, Chat, or BBB), which previously caused the frontend to hang in a perpetual loading state. The fix addresses two root causes: a lazy-loading ORM conflict during audit log serialization and missing None handling in WebSocket broadcast logic.

Key Changes:

  • Pre-warm the channel relationship after room creation to prevent lazy-loading issues during async serialization
  • Add defensive None checks in room config retrieval and WebSocket handlers to gracefully handle transient lookup failures
  • Skip WebSocket broadcasts when room configuration is temporarily unavailable instead of crashing

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
app/eventyay/base/services/event.py Pre-warms the channel relationship after creation and adds None check in get_room_config_for_user to handle room lookup failures
app/eventyay/features/live/modules/room.py Adds None checks in push_room_info and push_schedule_data WebSocket handlers to gracefully skip broadcasts when room config is unavailable

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mariobehling mariobehling merged commit 9f0b8d0 into fossasia:enext Dec 1, 2025
8 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Eventyay Next Dec 1, 2025
@mariobehling
Copy link
Member

I am still not able to create video rooms after the merge.

hongquan pushed a commit to hongquan/eventyay-tickets that referenced this pull request Dec 6, 2025
norbusan added a commit that referenced this pull request Dec 11, 2025
* Restructure configuration

* More settings

* More settings

* More settings

* More settings

* Use fixed DATA_DIR

* Specify how to provide secret settings

* Add more settings

* Delete reference to settings.TALK_CONFIG

* Set default running environment in some entry files

* Fix Docker build

* Fix missing software in Docker build

* Fix Docker build for prod

* No need to copy egg-info in Docker build

* Delete duplication in settings

* Update README with the guide for this new configuration system

* Print the list of configration files with colors

* Add .env support for config file

* Ignore .env file

Also print that we load .env file.

* Add "process" cache store

* Receover PRETIX_ADMIN_AUDIT_COMMENTS

* Recover PRETIX_xxx settings

* Fix crashing when encountering unsupported language

* Remove the --exclude option from Docker "COPY" command

* Do not add __pycache__ to the docker build

* Disable VueCompiler, not needed anymore

* Rework Dockerfile/entrypoint jobs

* move some python manage.py calls into Dockerfile to speed up
  generation of files.
* Keep compress in entrypoint.prod.sh.
* work around the fact that we need to have static.dist on a shared
  volume in docker (compose) so that nginx can share the files.

* Bring back VueCompiler

Because we still have Vue 2 code.

* feature: added hidden rooms and ensure sync (#1184)

* added unscheduled rooms

* implemented suggestions

* added suggestions by copilot

* fix(migrations): add missing migrations into base/0002

---------

Co-authored-by: Sak1012 <[email protected]>

* fix: add JSON_FIELD_AVAILABLE setting and fix Event.items reference

- Add JSON_FIELD_AVAILABLE setting based on database backend (postgresql = True)
- Fix checkinlists exporter using old Event.items instead of Event.products
- Resolves AttributeError when accessing export functionality

* fix rooms not working on video (#1339)

Co-authored-by: Srivatsav Auswin <[email protected]>
Co-authored-by: Mario Behling <[email protected]>

* Added translation using Weblate (Persian)

* Added translation using Weblate (Indonesian)

* Added translation using Weblate (Japanese)

* Added translation using Weblate (Korean)

* Added translation using Weblate (Malay)

* Added translation using Weblate (Vietnamese)

* fix(translations): remove old translation files and merge redundant files (#1370)

* fix(locale): Remove old translation files introduced by weblate

The updated translation files are already available in the new `enext`
system under `app/eventyay/locale`.

* fix(locale): Merge redundant language files

* fix: Fatal Server Error when creating room in video component (#1409)

* room creation null fix (#1414)

* add logging for room problem (#1415)

* add logging

* add defaults via migration

* fix(migrations): Add missing migrations (#1426)

* room complete setup pass (#1425)

* chore: save room fix try  (#1429)

* save room fix try and added logging

* implemented suggestions

---------

Co-authored-by: Mario Behling <[email protected]>

* Don't install Rollup to "static" dir

This Rollup is for Django-Compressor.
This "static" folder is for JS/CSS files that we will copy to Nginx serving folder.
It must not contain node_modules or JS tools.

* Update Makefile to install Rollup to new location

* Add missing 'crispy_forms_tags'

* Update pydantic-settings

* Delete the frontend built product files from "static" folder

* Delete duplicate static files

* Use "data/compiled-frontend" folder as output for Vue 3 apps

* Recover datetimepicker SCSS and JS files

They are needed by pretixcontrol app.

* Delete out-of-sync DB migration files.

These files are left after "enext" branch was force-pushed.

* Fix package-lock compatibility

* Revert unwanted changes due to enext force-push

* Fix ouput dir for building Vue 3 app in Dockerfile.prod

* Fix installing tool to build webcheckin in Dockerfile.prod

* Temporarily not run compilemessages in Dockerfile.prod

* Delete more duplicate static files

* Recover and relocate static files under "eventyay-common"

* Add diagram of how static files should be in folders

* Prevent DisallowedHost raised from MultiDomainMiddleware

* Don't install JS tools to "static" folder when building Docker

* Update `DJANGO_SETTINGS_MODULE` to new value in entire code base

* Set default `EVY_RUNNING_ENVIRONMENT` env var for Celery

* Changes to the deployment setup

* Raise the priority of env var for configuration

---------

Co-authored-by: Norbert Preining <[email protected]>
Co-authored-by: Saksham Sirohi <[email protected]>
Co-authored-by: Sak1012 <[email protected]>
Co-authored-by: Arnav Angarkar <[email protected]>
Co-authored-by: Srivatsav Auswin <[email protected]>
Co-authored-by: Mario Behling <[email protected]>
Co-authored-by: abbas davarpanah <[email protected]>
Co-authored-by: suhail nadaf <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Bug (Video): Unable to Create Rooms in Video Component – All Attempts Return “Fatal Server Error”

3 participants