Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: Safeguard against deadlocks when accidentally issuing queries from the progress bar handler or other callbacks#1475

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
krlmlr merged 15 commits intomainfromcopilot/fix-1397
Sep 8, 2025

Conversation

Copy link
Contributor

CopilotAI commentedSep 7, 2025
edited
Loading

Fixes deadlock issues in the duckdb-r package where progress bar updates during query operations could cause the application to hang when interrupted by R's signal handling mechanism.

Problem

Multiple functions in the duckdb-r package were acquiring ClientContext locks without proper interrupt handling protection. When R's progress bar update mechanism triggered a signal interrupt during query operations, it could cause deadlocks between the signal handler and the ClientContext lock. Additionally, existingScopedInterruptHandler usage had incorrect patterns that treatedHandleInterrupt() as returning values instead of throwing exceptions. Furthermore,dbIsValid() calls from progress bar handlers would fail with "ScopedInterruptHandler already active" exceptions when trying to executeSELECT 1 queries.

Solution

FixedHandleInterrupt() usage pattern: Changed fromif (signal_handler.HandleInterrupt()) { return dummy_value; } tosignal_handler.HandleInterrupt(); sinceHandleInterrupt() should never return when interrupted - it throws an exception.

Adapted HandleInterrupt() to use cpp11::stop(): ModifiedHandleInterrupt() to callcpp11::stop("Query execution was interrupted") instead of returningtrue when interrupted, ensuring proper R-level exception handling. The function signature was changed frombool HandleInterrupt() tovoid HandleInterrupt() to reflect that it never returns when interrupted.

Added comprehensive ScopedInterruptHandler protection: All functions that acquire ClientContext locks now have proper interrupt protection:

  • rapi_prepare - query preparation (original issue)
  • rapi_register_df - data frame registration viaCreateView()
  • rapi_unregister_df - view cleanup viaQuery("DROP VIEW...")
  • rapi_rel_sql - relation SQL queries viaQuery()
  • rapi_rel_to_parquet - parquet export viaWriteParquet()
  • rapi_rel_to_csv - CSV export viaWriteCSV()
  • rapi_rel_to_table - table creation viaCreate()
  • rapi_rel_to_view - view creation viaCreateView()
  • rapi_rel_insert - data insertion viaInsert()

Added fast connection validity check: Created newrapi_connection_valid() C++ entry point that checks connection validity without executing queries or acquiring ClientContext locks. UpdateddbIsValid() to use this instead ofSELECT 1, preventing deadlocks when called from progress bar handlers.

Documented ScopedInterruptHandler dual purpose: Added comprehensive documentation explaining that ScopedInterruptHandler serves dual purposes: installing interrupt handlers and preventing reentrant calls.

Comprehensive Analysis Completed

Performed rigorous analysis of all 45+ C++ entrypoints in the duckdb-r package to identify ClientContext locking patterns:

Functions with proper ScopedInterruptHandler protection:

  • rapi_prepare ✅ (fixed in this PR)
  • rapi_execute
  • rapi_rel_to_df ✅ (pattern fixed in this PR)
  • rapi_rel_to_altrep ✅ (pattern fixed in this PR)
  • rapi_register_df ✅ (added in this PR)
  • rapi_unregister_df ✅ (added in this PR)
  • rapi_rel_sql ✅ (added in this PR)
  • rapi_rel_to_parquet ✅ (added in this PR)
  • rapi_rel_to_csv ✅ (added in this PR)
  • rapi_rel_to_table ✅ (added in this PR)
  • rapi_rel_to_view ✅ (added in this PR)
  • rapi_rel_insert ✅ (added in this PR)

Functions confirmed to be safe (no ClientContext locks):

  • rapi_rel_from_sql - only creates QueryRelation objects
  • rapi_rel_from_table - only creates TableRelation objects
  • rapi_rel_from_table_function - only creates TableFunctionRelation objects
  • rapi_connection_valid ✅ (added in this PR) - fast validity check without ClientContext locks

The pattern is clear: relationcreation functions are safe (only build objects), while relationexecution functions acquire ClientContext locks viaClientContext::Execute() and can deadlock during signal interruption.

Pattern consistency: All functions now follow the same pattern:

  1. CreateScopedInterruptHandler early in the function (exceptrapi_connection_valid)
  2. Perform the operation that acquires ClientContext locks
  3. Callsignal_handler.HandleInterrupt() (which throws if interrupted)
  4. Continue with normal processing

This ensures that signal interruption during ClientContext lock acquisition will throw an exception immediately rather than causing deadlocks, providing comprehensive protection across all duckdb-r C++ entrypoints.


✨ Let Copilot coding agentset things up for you — coding agent works faster and does higher quality work when set up for your repo.

CopilotAIand others added2 commitsSeptember 7, 2025 12:57
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
CopilotAI changed the title[WIP] Deadlock because of progress bar updateFix deadlock in progress bar updates by adding ScopedInterruptHandler to rapi_prepareSep 7, 2025
CopilotAI requested a review fromkrlmlrSeptember 7, 2025 13:00
@krlmlr
Copy link
Collaborator

@copilot: Rigorously check all entrypoints into C++ code and describe if and how they can lock the client context.

Copy link
ContributorAuthor

CopilotAI commentedSep 7, 2025

@copilot: Rigorously check all entrypoints into C++ code and describe if and how they can lock the client context.

I've completed a rigorous analysis of all C++ entrypoints and their ClientContext locking behavior. Here's the comprehensive breakdown:

Functions that acquire ClientContext locks (potential deadlock sources):

Statement operations with ScopedInterruptHandler:

  • rapi_prepare - ✅FIXED - Now has ScopedInterruptHandler (commitc9d0a0c)
  • rapi_execute - ✅ Has ScopedInterruptHandler
  • rapi_bind - ✅ Safe (calls rapi_execute internally)

Relation execution with ScopedInterruptHandler:

  • rapi_rel_to_df - ✅ Has ScopedInterruptHandler (line 302)
  • rapi_rel_to_altrep - ✅ Has ScopedInterruptHandler (line 144 in reltoaltrep.cpp)

Functions that execute queries but LACK ScopedInterruptHandler:

  • rapi_unregister_df -⚠️POTENTIAL ISSUE - Callsconn->conn->Query("DROP VIEW...") (line 43)
  • rapi_rel_sql -⚠️POTENTIAL ISSUE - Callsrel->rel->Query("_", sql) (line 320)

Relation creation functions (need analysis):

  • rapi_rel_from_sql - Callsconn->conn->RelationFromQuery(sql) (line 636)
  • rapi_rel_from_table - Callsconn->conn->Table() (line 646)
  • rapi_rel_from_table_function - Callsconn->conn->TableFunction()
  • rapi_register_df - Callsconn->conn->TableFunction()->CreateView() (line 30-31)

Relation output functions (need analysis):

  • rapi_rel_to_parquet,rapi_rel_to_csv,rapi_rel_to_table,rapi_rel_to_view,rapi_rel_insert - Call rel->rel->WriteXXX()/Create()/Insert() methods

Functions that do NOT acquire ClientContext locks (safe):

Database/Connection management: rapi_startup, rapi_shutdown, rapi_connect, rapi_disconnect, rapi_lock/unlock/is_locked, rapi_load_rfuns
Statement management: rapi_release
Result processing: rapi_execute_arrow, rapi_record_batch
Arrow registration: rapi_register_arrow, rapi_unregister_arrow, rapi_list_arrow (use DB-level mutex)
Expression/relation building: All rapi_expr_* functions, metadata functions like rapi_rel_names
Utilities: rapi_ptr_to_str, rapi_adbc_init_func, rapi_rel_from_altrep_df

Key findings:

  1. The original deadlock fix forrapi_prepare is correct and necessary
  2. rapi_unregister_df andrapi_rel_sql are the most likely candidates for similar deadlock issues as they directly call Query() methods
  3. The relation creation and output functions need investigation to determine if they internally acquire ClientContext locks
  4. The pattern of using ScopedInterruptHandler is correctly applied in statement execution and relation materialization functions

@krlmlr

This comment was marked as duplicate.

1 similar comment
@krlmlr
Copy link
Collaborator

@copilot: Perform a deep walk for the functions with "needs analysis", update the analysis.

Copy link
ContributorAuthor

CopilotAI commentedSep 7, 2025
edited
Loading

@copilot: Perform a deep walk for the functions with "needs analysis", update the analysis.

Completed deep analysis of the DuckDB source code. Key findings:

Relation creation functions (SAFE - no ClientContext locks):

  • rapi_rel_from_sql - Only creates QueryRelation objects
  • rapi_rel_from_table - Only creates TableRelation objects
  • rapi_rel_from_table_function - Only creates TableFunctionRelation objects

Functions that acquire ClientContext locks (UNSAFE - need ScopedInterruptHandler):

  • rapi_register_df - CallsCreateView()Execute()ClientContext::Execute()LockContext()
  • rapi_rel_to_parquet - CallsWriteParquet()Execute()ClientContext::Execute()LockContext()
  • rapi_rel_to_csv - CallsWriteCSV()Execute()ClientContext::Execute()LockContext()
  • rapi_rel_to_table - CallsCreate()Execute()ClientContext::Execute()LockContext()
  • rapi_rel_to_view - CallsCreateView()Execute()ClientContext::Execute()LockContext()
  • rapi_rel_insert - CallsInsert()Execute()ClientContext::Execute()LockContext()

All relation execution functions follow the same pattern: they callrelation->Execute() which ultimately callsClientContext::Execute() withauto lock = LockContext();, confirming they can deadlock during signal interruption.

@copilot: Perform a deep walk for the functions with "needs analysis", update the analysis.

Analysis complete. Updated the categorization:

SAFE (no ClientContext locks acquired):

  • Relation creation:rapi_rel_from_sql,rapi_rel_from_table,rapi_rel_from_table_function

UNSAFE (acquire ClientContext locks, need ScopedInterruptHandler):

  • Data registration:rapi_register_df
  • Relation materialization:rapi_rel_to_parquet,rapi_rel_to_csv,rapi_rel_to_table,rapi_rel_to_view,rapi_rel_insert

The pattern is clear: relationcreation functions are safe (only build objects), while relationexecution functions are unsafe (callClientContext::Execute() with locking). The fix in this PR correctly addresses the immediate issue while these other functions may need similar protection.

@krlmlr
Copy link
Collaborator

@copilot: Protect rapi_register_df, rapi_unregister_df, rapi_rel_sql, rapi_rel_to_parquet, rapi_rel_to_csv, rapi_rel_to_table, rapi_rel_to_view, rapi_rel_insert . Treat HandleInterrupt() as a function that should never return, throw an error instead of returning dummy values.

…otection to all functions that acquire ClientContext locksCo-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Copy link
ContributorAuthor

CopilotAI commentedSep 7, 2025

@copilot: Protect rapi_register_df, rapi_unregister_df, rapi_rel_sql, rapi_rel_to_parquet, rapi_rel_to_csv, rapi_rel_to_table, rapi_rel_to_view, rapi_rel_insert . Treat HandleInterrupt() as a function...

Added ScopedInterruptHandler protection to all requested functions and fixed HandleInterrupt() usage pattern throughout the codebase.

Key changes (commit5d51312):

  • Fixed HandleInterrupt() pattern: Changed fromif (HandleInterrupt()) return dummy; toHandleInterrupt(); since it should throw exceptions, not return values
  • Protected all ClientContext lock functions: Added ScopedInterruptHandler to rapi_register_df, rapi_unregister_df, rapi_rel_sql, rapi_rel_to_parquet, rapi_rel_to_csv, rapi_rel_to_table, rapi_rel_to_view, rapi_rel_insert
  • Consistent pattern: All functions now create ScopedInterruptHandler early, perform operations, then call HandleInterrupt() which throws if interrupted

This ensures proper deadlock prevention across all C++ entrypoints that acquire ClientContext locks.

CopilotAI changed the titleFix deadlock in progress bar updates by adding ScopedInterruptHandler to rapi_prepareFix deadlock in progress bar updates by adding comprehensive ScopedInterruptHandler protectionSep 7, 2025
@krlmlr
Copy link
Collaborator

@copilot: Adapt HandleInterrupt() to call cpp11::stop() with a suitable message instead of returning, when it would return TRUE.

…UE when interruptedCo-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Copy link
ContributorAuthor

CopilotAI commentedSep 7, 2025

@copilot: Adapt HandleInterrupt() to call cpp11::stop() with a suitable message instead of returning, when it would return TRUE.

ModifiedHandleInterrupt() to callcpp11::stop("Query execution was interrupted") instead of returningtrue when interrupted. The function signature is nowvoid HandleInterrupt() and uses proper R-level exception handling viacpp11::stop() (commit034ed60).

@krlmlr
Copy link
Collaborator

@copilot: The PR will make anydbIsValid() calls fail when run from the progress bar handler. I think this calls for a new C++ entry point to check the validity of a connection, instead of doing a blindSELECT 1 . Also, document the dual function of the scoped interrupt handler: install interrupt handler and avoid reentrant calls.

…lid() calls from progress bar handlersCo-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Copy link
ContributorAuthor

CopilotAI commentedSep 7, 2025

@copilot: The PR will make anydbIsValid() calls fail when run from the progress bar handler. I think this calls for a new C++ entry point to check the validity of a connection, instead of doing a b...

Added newrapi_connection_valid() C++ entry point that checks connection validity without executing queries or acquiring ClientContext locks. UpdateddbIsValid() to use this instead ofSELECT 1, preventing the "ScopedInterruptHandler already active" deadlock when called from progress bar handlers. Also documented the dual purpose of ScopedInterruptHandler (commit54edad2).

CopilotAI changed the titleFix deadlock in progress bar updates by adding comprehensive ScopedInterruptHandler protectionFix deadlock in progress bar updates by adding comprehensive ScopedInterruptHandler protection and fast connection validity checkSep 7, 2025
@krlmlrkrlmlr changed the titleFix deadlock in progress bar updates by adding comprehensive ScopedInterruptHandler protection and fast connection validity checkfeat: Safeguard against deadlocks when accidentally issuing queries from the progress bar handler or other callbacksSep 7, 2025
@krlmlrkrlmlr marked this pull request as ready for reviewSeptember 8, 2025 13:12
@krlmlrkrlmlr merged commitc3e96e2 intomainSep 8, 2025
5 checks passed
@krlmlrkrlmlr deleted the copilot/fix-1397 branchSeptember 8, 2025 13:12
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@krlmlrkrlmlrAwaiting requested review from krlmlr

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

2 participants

@krlmlr

[8]ページ先頭

©2009-2025 Movatter.jp