Skip to content

Conversation

philippotto
Copy link
Member

@philippotto philippotto commented Feb 25, 2025

See redux-saga/redux-saga#1592 for the underlying issue. Avoiding high amounts of yield within markBucketsAsNotDirty should fix the stuck saving issue. Ideally, we would also add measures so that the same problem cannot happen silently again, but I haven't found a way for this (yet).

URL of deployed dev instance (used for testing):

  • https://___.webknossos.xyz

Steps to test:

  • brush a lot and hit save -> everything should work

TODOs:

  • avoid that this silently fails?

Issues:

  • fixes stuck saving (e.g., see internal report here)

(Please delete unneeded items, merge only when none are left open)

Copy link
Contributor

coderabbitai bot commented Feb 25, 2025

📝 Walkthrough

Walkthrough

The pull request introduces a new changelog entry documenting a bug fix for an unresponsive saving process. It corrects a spelling error in a comment and updates various generator functions’ return types from Saga<void> to Saga<never>, ensuring type consistency. Additionally, it refines state update methods and error handling in some sagas, incorporates memoization for performance in one saga, and explicitly annotates a forever-running function’s return type as Promise<never>.

Changes

File(s) Change Summary
CHANGELOG.unreleased.md Added an entry in the “Fixed” section documenting a bug fix for the unresponsive saving process.
frontend/…/actions/save_actions.ts Corrected a comment by changing “fulfil” to “fulfill” (American English).
frontend/…/reducers/save_reducer.ts Replaced updateKey2 with updateKey to update the lastSaveTimestamp state.
frontend/…/sagas/annotation_saga.tsx, frontend/…/sagas/annotation_tool_saga.ts, frontend/…/sagas/mapping_saga.ts, frontend/…/sagas/quick_select_ml_saga.ts, frontend/…/sagas/root_saga.ts Updated generator function signatures by changing return types from Saga<void> to Saga<never>.
frontend/…/sagas/mesh_saga.ts Changed return type from Saga<void> to Saga<never> and modified error handling (using continue instead of error throwing or breaking).
frontend/…/sagas/save_saga.ts Updated multiple saga function return types to Saga<never>, and introduced memoization with memoizeOne for optimizing bucket processing.
frontend/…/throttled_store.ts Explicitly declared the go() function’s return type as Promise<never> to signal its indefinite execution.

Possibly related PRs

Suggested labels

bug, performance, automerge

Suggested reviewers

  • daniel-wer
  • MichaelBuessemeyer

Poem

I'm a coding bunny, hopping through lines so neat,
Fixing bugs and typos with a skip and a beat.
My sagas run forever, like a loop that never ends,
With memoized tricks and tweaks that please my rabbit friends.
From changelog to state updates, I celebrate each change with a joyful leap! 🐰

Tip

CodeRabbit's docstrings feature is now available as part of our Pro Plan! Simply use the command @coderabbitai generate docstrings to have CodeRabbit automatically generate docstrings for your pull request. We would love to hear your feedback on Discord.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c09afa9 and 9e89b9b.

📒 Files selected for processing (11)
  • CHANGELOG.unreleased.md (1 hunks)
  • frontend/javascripts/oxalis/model/actions/save_actions.ts (1 hunks)
  • frontend/javascripts/oxalis/model/reducers/save_reducer.ts (1 hunks)
  • frontend/javascripts/oxalis/model/sagas/annotation_saga.tsx (1 hunks)
  • frontend/javascripts/oxalis/model/sagas/annotation_tool_saga.ts (2 hunks)
  • frontend/javascripts/oxalis/model/sagas/mapping_saga.ts (1 hunks)
  • frontend/javascripts/oxalis/model/sagas/mesh_saga.ts (2 hunks)
  • frontend/javascripts/oxalis/model/sagas/quick_select_ml_saga.ts (1 hunks)
  • frontend/javascripts/oxalis/model/sagas/root_saga.ts (1 hunks)
  • frontend/javascripts/oxalis/model/sagas/save_saga.ts (6 hunks)
  • frontend/javascripts/oxalis/throttled_store.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/javascripts/oxalis/model/actions/save_actions.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: circleci_build
🔇 Additional comments (16)
CHANGELOG.unreleased.md (1)

21-21: Good addition to document the bug fix.

The changelog entry accurately describes the issue that was fixed in this PR, providing users with information about the resolved problem.

frontend/javascripts/oxalis/throttled_store.ts (1)

21-21: Improved type safety with explicit Promise return type.

This change correctly annotates the go function with Promise<never> return type, which is appropriate for a function containing an infinite loop. The function is indeed designed to run forever without resolving.

frontend/javascripts/oxalis/model/reducers/save_reducer.ts (1)

160-160: Improved state update pattern.

Changing from updateKey2 to updateKey is a good refactoring that maintains the same functionality but uses a more consistent state update pattern. This aligns with the PR's goal of optimizing state updates related to saving.

frontend/javascripts/oxalis/model/sagas/mapping_saga.ts (1)

228-228: Improved type safety with explicit Saga return type.

This change correctly annotates the watchChangedBucketsForLayer function with Saga<never> return type, which is appropriate for a saga containing an infinite loop. This is consistent with similar changes in other saga files mentioned in the PR summary.

frontend/javascripts/oxalis/model/sagas/annotation_saga.tsx (1)

153-153: Improved type safety with Saga<never> return type.

The return type change from Saga<void> to Saga<never> accurately reflects that this function runs in an infinite loop and never completes normally. This is a more precise type annotation that aligns with the actual behavior of the saga.

frontend/javascripts/oxalis/model/sagas/root_saga.ts (1)

26-26: Correct return type for infinite loop saga.

Changing the return type to Saga<never> properly indicates that this saga runs indefinitely in its while(true) loop and is not meant to complete normally. This is a good type safety improvement.

frontend/javascripts/oxalis/model/sagas/quick_select_ml_saga.ts (1)

133-136: Accurate return type for continuous progress indicator.

Adding the Saga<never> return type correctly reflects that this progress indicator runs in an infinite loop and requires explicit cancellation (which happens in the finally block of the caller at line 304). This type annotation is more precise than the previous Saga<void>.

frontend/javascripts/oxalis/model/sagas/annotation_tool_saga.ts (2)

16-16: Return type accurately reflects infinite saga.

The Saga<never> return type correctly indicates that this function runs in a continuous loop and won't complete normally, which matches its implementation.


41-41: Appropriate return type for infinite tool reset watcher.

The Saga<never> return type properly reflects that this function continuously watches for tool reset events in an infinite loop, which is consistent with its implementation.

frontend/javascripts/oxalis/model/sagas/mesh_saga.ts (2)

1202-1202: Return type updated from Saga to Saga

This is a good change. Since this saga function runs in an infinite loop with while(true), it will never actually return. Using Saga<never> instead of Saga<void> properly reflects this behavior and is more type-safe.


1215-1215: Fixed error handling to continue instead of throwing

This change improves the saga's resilience. Rather than throwing an error when an unexpected action type is encountered (even though the saga already filtered for ["SET_ADDITIONAL_COORDINATES"]), the saga will now continue listening for the next action.

frontend/javascripts/oxalis/model/sagas/save_saga.ts (5)

57-57: Added memoizeOne import for performance optimization

Good addition for performance optimization, especially since this will be used to optimize the bucket processing function.


61-61: Return type updated from Saga to Saga

Appropriate type change. Since this saga runs in an infinite loop with while(true), it will never actually return. Using Saga<never> instead of Saga<void> properly reflects this behavior and is more type-safe.


292-292: Using the memoized function

Properly using the memoized function to get layer and magInfo for the tracing ID, replacing the previous direct calculation with cached results when possible.


383-383: Return type updated from Saga to Saga

Correct type change. This saga also runs in an infinite loop and will never return normally.


456-456: Return type updated from Saga to Saga

Appropriate type change for this infinite loop saga, consistent with similar changes in other functions.

const meshRecords = segmentMeshController.meshesGroupsPerSegmentId;

if (action.values == null || action.values.length === 0) break;
if (action.values == null || action.values.length === 0) continue;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Changed break to continue when values are empty

This is a crucial fix that likely addresses the core issue. Previously, the saga would exit with break when action.values was null or empty, which would terminate the saga permanently. Changing to continue ensures the saga keeps listening for subsequent actions, maintaining its long-running nature.

Comment on lines 282 to +287
function* markBucketsAsNotDirty(saveQueue: Array<SaveQueueEntry>) {
const getLayerAndMagInfoForTracingId = memoizeOne((tracingId: string) => {
const segmentationLayer = Model.getSegmentationTracingLayer(tracingId);
const segmentationMagInfo = getMagInfo(segmentationLayer.mags);
return [segmentationLayer, segmentationMagInfo] as const;
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Added memoization to optimize bucket processing

Great performance optimization! This directly addresses the PR objective by reducing redundant calculations in the markBucketsAsNotDirty function, which was causing the application to become unresponsive when processing a large number of buckets at once.

The memoization ensures that for a given tracingId, the layer and magInfo are only computed once, which can significantly improve performance when processing thousands of buckets.

Copy link
Contributor

@MichaelBuessemeyer MichaelBuessemeyer left a comment

Choose a reason for hiding this comment

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

Can confirm, works 🎉

Thanks for fixing this and the other miscellaneous improvements

Comment on lines +1215 to +1219
continue;
}
const meshRecords = segmentMeshController.meshesGroupsPerSegmentId;

if (action.values == null || action.values.length === 0) break;
if (action.values == null || action.values.length === 0) continue;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this ok to change? The previously thrown error is now swallowed

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, the handling is only done for TS, as far as I understand. I can output a warning to console, though.

Comment on lines -288 to +292
const segmentationLayer = Model.getSegmentationTracingLayer(tracingId);
const segmentationMagInfo = yield* call(getMagInfo, segmentationLayer.mags);
const [segmentationLayer, segmentationMagInfo] = getLayerAndMagInfoForTracingId(tracingId);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is the fix, correct?

Strange that non-nested yield calls can lead to a stack overflow :/

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, apparently, the effects are processed tail recursively by redux which leads to this problem.

@philippotto philippotto enabled auto-merge (squash) February 26, 2025 07:19
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/javascripts/oxalis/model/sagas/save_saga.ts (1)

104-104: Fixed comment typo

Simple spelling fix in the comments for better readability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e89b9b and 6c9113c.

📒 Files selected for processing (2)
  • frontend/javascripts/oxalis/model/sagas/mesh_saga.ts (2 hunks)
  • frontend/javascripts/oxalis/model/sagas/save_saga.ts (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/javascripts/oxalis/model/sagas/mesh_saga.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: circleci_build
🔇 Additional comments (6)
frontend/javascripts/oxalis/model/sagas/save_saga.ts (6)

10-10: Adding memoization dependency for performance optimization

Good addition. This new dependency will be used to optimize the bucket processing function, which is key to fixing the performance issue mentioned in the PR objectives.


61-61: Improved type safety with Saga<never> return type

This return type more accurately reflects that this saga runs infinitely and doesn't normally return. This is a good practice for long-running sagas that helps prevent accidental early termination.


282-287: Added memoization to optimize bucket processing

Great performance optimization! This directly addresses the PR objective by reducing redundant calculations in the markBucketsAsNotDirty function, which was causing the application to become unresponsive when processing a large number of buckets at once.

The memoization ensures that for a given tracingId, the layer and magInfo are only computed once, which can significantly improve performance when processing thousands of buckets.


292-292: Using memoized function to retrieve layer and magInfo

Good implementation. This line applies the memoized function created above, ensuring that repeated calls with the same tracingId reuse the cached result rather than recalculating.


383-383: Updated return type for consistency and accuracy

Similar to the change for pushSaveQueueAsync, this provides more accurate typing for a saga that runs continuously.


456-456: Added missing return type annotation

The consistent application of Saga<never> here maintains type safety across all infinite-running sagas.

@philippotto philippotto merged commit 5444647 into master Feb 26, 2025
3 checks passed
@philippotto philippotto deleted the fix-stuck-saving branch February 26, 2025 07:32
philippotto added a commit that referenced this pull request Feb 26, 2025
…8409)

* tmp: try to debug stuck saving

* tmp: attempts to better guard against silent stack overflow

* clean up

* fix stuck saving

* further clean up

* clean up

* update changelog

* format

* add console.error in case of ts error
@coderabbitai coderabbitai bot mentioned this pull request Feb 26, 2025
2 tasks
philippotto added a commit that referenced this pull request Feb 26, 2025
* Create Release 25.02.0

* Fix stuck saving when more than ~1500 buckets were labeled at once (#8409)

* tmp: try to debug stuck saving

* tmp: attempts to better guard against silent stack overflow

* clean up

* fix stuck saving

* further clean up

* clean up

* update changelog

* format

* add console.error in case of ts error

* Release 25.02.1

---------

Co-authored-by: Florian M <florian@scm.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants