Skip to content

Thread safety#210

Merged
ueno merged 5 commits intoueno:masterfrom
djberg96:thread_safety
Feb 9, 2026
Merged

Thread safety#210
ueno merged 5 commits intoueno:masterfrom
djberg96:thread_safety

Conversation

@djberg96
Copy link
Contributor

@djberg96 djberg96 commented Feb 7, 2026

Improve thread safety of ruby-gpgme. Hopefully addresses #115.

The ruby-gpgme gem has no thread safety mechanisms, which can cause "Bad file descriptor" errors when multiple threads perform GPG operations concurrently (e.g. in Rails/Sidekiq environments). The root causes include unsafe GC behavior with the C extension, lack of synchronization, and fragile fd handling in the passphrase callback.

Changes:

Removed RUBY_TYPED_FREE_IMMEDIATELY from all four typed data structs (Data, Ctx, Key, TrustItem). This flag allowed Ruby's GC to free the underlying C structs outside the GVL, which is unsafe when another thread may still be using them. Setting flags to 0 defers freeing to a safe point.

Added RB_GC_GUARD calls after blocking GPGME operations in 7 functions: gpgme_op_decrypt, gpgme_op_decrypt_verify, gpgme_op_sign, gpgme_op_encrypt, gpgme_op_encrypt_sign, gpgme_op_verify, and gpgme_op_keylist_next. This prevents the GC from collecting Ruby-wrapped objects (contexts, data buffers) while the C library is still using their underlying pointers.

At the ruby layer:

Added opt-out thread-safe mode via GPGME.thread_safe = true. When enabled, all block-form Ctx.new calls are serialized through a global Monitor (reentrant mutex). A Monitor is used rather than a Mutex because GPGME operations are reentrant — e.g. Crypto#sign → Ctx.new → Key.find → Ctx.new.

Improved Ctx.pass_function fd handling: uses explicit io.write instead of io.puts, sets autoclose = false to prevent Ruby from closing the fd that belongs to GPGME/gpg-agent, and wraps the write in error handling.

Usage

Thread-safe mode is on by default but can be altered GPGME.thread_safe = false if it's proving problematic for some reason.

Copy link
Owner

@ueno ueno left a comment

Choose a reason for hiding this comment

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

Looks very reasonable, thank you!

@ueno ueno merged commit af6c2e9 into ueno:master Feb 9, 2026
12 checks passed
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