Skip to content

fix(map.lic): v2.1.1 gtk crash fix#2280

Merged
mrhoribu merged 4 commits intomasterfrom
fix/map.lic-v2.1.1
Apr 10, 2026
Merged

fix(map.lic): v2.1.1 gtk crash fix#2280
mrhoribu merged 4 commits intomasterfrom
fix/map.lic-v2.1.1

Conversation

@mrhoribu
Copy link
Copy Markdown
Contributor

@mrhoribu mrhoribu commented Mar 21, 2026

Summary by CodeRabbit

Release Notes - v2.1.1

  • Bug Fixes
    • Fixed crashes during application shutdown and window destruction.
    • Resolved memory leaks in dynamic menu handling.
    • Improved stability of window positioning and display.
    • Enhanced resource cleanup for better overall performance.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 21, 2026

📝 Walkthrough

Walkthrough

Bumped scripts/map.lic to v2.1.1; added GC anchor ivars for long-lived and dynamic GTK handlers, replaced inline timeout with an ivar-stored Proc, refactored ordered/synchronous window cleanup, and adjusted monitor geometry calls to avoid GC-related crashes.

Changes

Cohort / File(s) Summary
Map script
scripts/map.lic
Version bump to v2.1.1. Added GC anchor ivars @_prevent_gc (persistent) and @_dynamic_menu_gc (cleared on submenu rebuild). Replaced block-based signal_connect handlers with Proc variables stored in anchors. Cleared dynamic anchors before regenerating Scale/Tags/Locations submenus. Replaced inline GLib::Timeout.add block with @_timeout_proc stored Proc. Refactored ElanthiaMap::Window#destroy to enqueue ordered cleanup (note-pin widgets → markers → menus → layout children → destroy GTK window), wait synchronously for queued cleanup via a bounded polling loop, set @gtk_window = nil, and clear GC anchors. Broke chained Gdk::Screen calls into intermediates to mitigate position_window GC crash risk.

Sequence Diagram

sequenceDiagram
    participant Script as Main Script
    participant Window as ElanthiaMap::Window
    participant Queue as Gtk.queue
    participant Cleanup as Cleanup Block
    participant GTK as GTK Engine
    participant GC as Ruby GC

    Script->>Window: call destroy
    Window->>Window: set completed = false, arrange cleanup state
    Window->>Queue: queue Cleanup block (captures Procs/widgets via anchors)
    Note right of Window: bounded polling loop waits for completed
    Queue->>Cleanup: run queued cleanup
    Cleanup->>Cleanup: destroy note-pin widgets -> markers -> menus -> layout children
    Cleanup->>GTK: destroy `@gtk_window`, disconnect signal handlers
    Cleanup->>Window: set completed = true, clear `@_dynamic_menu_gc` and `@_prevent_gc`
    Window->>Script: polling sees completed, return
    Script->>GC: Procs/handlers become eligible for collection
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix(map.lic): v2.1.1 gtk crash fix' directly relates to the main changes: fixing GTK crashes through GC anchoring, signal handler refactoring, and synchronous destruction cleanup in map.lic.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/map.lic-v2.1.1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/map.lic (1)

1579-1605: ⚠️ Potential issue | 🟠 Major

Unanchored scale submenu items when global mode is enabled.

When global_scale_enabled is true at line 1584, the scale submenu rebuild is skipped. However, line 1581 clears @_dynamic_menu_gc unconditionally for all menus, releasing the old scale submenu items before that conditional check. These items—which contain Procs with signal handlers—remain in the GTK widget tree but lose their Ruby anchor, leaving them vulnerable to garbage collection during normal operation while GTK still references them. Either use a fresh anchor array and keep the previous one alive until replaced submenus are destroyed, or rebuild the scale submenu regardless of the global_scale_enabled setting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/map.lic` around lines 1579 - 1605, Clearing the global anchor array
`@_dynamic_menu_gc` before you decide whether to rebuild the scale submenu
unanchors Procs for `@menu_scale` when `@settings`[:global_scale_enabled] is true;
either preserve the old anchors until the submenu is explicitly destroyed or
always rebuild the scale submenu. Fix by changing the order in the refresh
logic: either (A) move `@_dynamic_menu_gc.clear` into each menu-specific branch so
you only clear anchors for menus you rebuild (references: `@_dynamic_menu_gc`,
`@menu_scale`, `@menu_tags`, `@menu_locations`, build_scale_submenu), or (B) allocate
a fresh anchor array (old_anchor = `@_dynamic_menu_gc`; `@_dynamic_menu_gc` = [])
and only discard old_anchor after you have destroyed/replaced the corresponding
submenu widgets so the Procs remain anchored while GTK still holds them.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/map.lic`:
- Around line 602-670: The destroy logic currently relies only on the
`@_timeout_proc` ivar and never cancels scheduled GLib timeouts, so earlier
timeouts can leak C-side references; modify the code that schedules callbacks
(where GLib::Timeout.add(50, &@_timeout_proc) is called) to capture and store
the returned source id in a dedicated ivar (e.g. `@_timeout_source_id` or
similar), and in the destroy method (the block that runs before clearing
`@_prevent_gc` and `@_dynamic_menu_gc`) call
GLib::Timeout.remove(`@_timeout_source_id`) for any existing source id (and
clear/reset both `@_timeout_source_id` and `@_timeout_proc`) before releasing GC
anchors to ensure pending timeouts are canceled.

---

Outside diff comments:
In `@scripts/map.lic`:
- Around line 1579-1605: Clearing the global anchor array `@_dynamic_menu_gc`
before you decide whether to rebuild the scale submenu unanchors Procs for
`@menu_scale` when `@settings`[:global_scale_enabled] is true; either preserve the
old anchors until the submenu is explicitly destroyed or always rebuild the
scale submenu. Fix by changing the order in the refresh logic: either (A) move
`@_dynamic_menu_gc.clear` into each menu-specific branch so you only clear anchors
for menus you rebuild (references: `@_dynamic_menu_gc`, `@menu_scale`, `@menu_tags`,
`@menu_locations`, build_scale_submenu), or (B) allocate a fresh anchor array
(old_anchor = `@_dynamic_menu_gc`; `@_dynamic_menu_gc` = []) and only discard
old_anchor after you have destroyed/replaced the corresponding submenu widgets
so the Procs remain anchored while GTK still holds them.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bbb83393-036c-4c26-a5b7-ecf69b3023b5

📥 Commits

Reviewing files that changed from the base of the PR and between 86379e4 and 511be4d.

📒 Files selected for processing (1)
  • scripts/map.lic

Comment thread scripts/map.lic
@mrhoribu
Copy link
Copy Markdown
Contributor Author

@coderabbitai review again

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 25, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 25, 2026

@mrhoribu Sure! Triggering a review of the PR now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…C safety

Updated signal handler connections to use Ruby Procs for better garbage collection management. Improved crash fixes and menu item handling.
Copy link
Copy Markdown
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.

♻️ Duplicate comments (1)
scripts/map.lic (1)

2067-2077: ⚠️ Potential issue | 🟡 Minor

Store and cancel pending timeout in destroy.

The proc is correctly anchored, but GLib::Timeout.add returns a source_id that should be stored to allow cancellation. If destroy() is called while a timeout is pending, the callback may fire on a partially-destroyed window. Store the source_id and call GLib::Timeout.remove() in destroy() before clearing GC anchors.

,

🛡️ Proposed fix to store and cancel timeout
               # Wait a moment for the layout to be realized with new size
               # Store Proc in ivar to prevent GC before GLib invokes it
               `@_timeout_proc` = proc do
                 begin
                   center_viewport_on(center_x, center_y)
                 rescue StandardError
                   # Ignore if window is being destroyed
                 end
                 `@_timeout_proc` = nil
+                `@_timeout_source_id` = nil
                 false # Don't repeat
               end
-              GLib::Timeout.add(50, &@_timeout_proc)
+              `@_timeout_source_id` = GLib::Timeout.add(50, &@_timeout_proc)

And in destroy(), before clearing GC anchors (around line 663):

+            # Cancel any pending timeout
+            if `@_timeout_source_id`
+              GLib::Timeout.remove(`@_timeout_source_id`) rescue nil
+              `@_timeout_source_id` = nil
+              `@_timeout_proc` = nil
+            end
+
             # Release all GC-prevention refs now that widgets are destroyed
             `@_prevent_gc.clear`
             `@_dynamic_menu_gc.clear`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/map.lic` around lines 2067 - 2077, The timeout source_id returned by
GLib::Timeout.add is not stored and thus cannot be canceled; modify the code
around GLib::Timeout.add to assign its return to an instance variable (e.g.
`@_timeout_source_id`) while keeping `@_timeout_proc` to prevent GC, and update
destroy (the destroy method) to check for and call
GLib::Timeout.remove(`@_timeout_source_id`) (and clear `@_timeout_source_id` and
`@_timeout_proc`) before clearing other GC anchors so the pending callback cannot
run on a partially-destroyed window.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@scripts/map.lic`:
- Around line 2067-2077: The timeout source_id returned by GLib::Timeout.add is
not stored and thus cannot be canceled; modify the code around GLib::Timeout.add
to assign its return to an instance variable (e.g. `@_timeout_source_id`) while
keeping `@_timeout_proc` to prevent GC, and update destroy (the destroy method) to
check for and call GLib::Timeout.remove(`@_timeout_source_id`) (and clear
`@_timeout_source_id` and `@_timeout_proc`) before clearing other GC anchors so the
pending callback cannot run on a partially-destroyed window.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6d3df4b0-e3b2-466c-9bfb-4b42eeb0cdd8

📥 Commits

Reviewing files that changed from the base of the PR and between eadd73e and c805d9a.

📒 Files selected for processing (1)
  • scripts/map.lic

@mrhoribu mrhoribu merged commit 994b659 into master Apr 10, 2026
4 checks passed
@mrhoribu mrhoribu deleted the fix/map.lic-v2.1.1 branch April 10, 2026 20:10
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.

1 participant