Skip to content

feat: add sw_threading plugin for automatic cross-thread context propagation#390

Closed
wu-sheng wants to merge 2 commits intomasterfrom
feat/threading-plugin
Closed

feat: add sw_threading plugin for automatic cross-thread context propagation#390
wu-sheng wants to merge 2 commits intomasterfrom
feat/threading-plugin

Conversation

@wu-sheng
Copy link
Copy Markdown
Member

Summary

  • Add new sw_threading plugin that patches Thread.__init__ and Thread.run to automatically propagate trace context across threads
  • Update @runnable decorator to read snapshot from the Thread object at execution time instead of capturing at decoration time
  • When sw_threading detects the target is @runnable-wrapped, it defers span creation to @runnable

This fixes the issue where @runnable only worked when applied inline during an active request. Module-level @runnable (the natural Python pattern) silently broke cross-thread trace linking because the snapshot was captured at import time when no trace context existed.

With the new plugin, users don't need @runnable at all — just pass a plain function to Thread and the context is propagated automatically:

def post():
    requests.post('http://provider:9091/users')

@app.route('/users')
def application():
    thread = Thread(target=post)
    thread.start()

Existing @runnable usage (both inline and module-level) continues to work.

Closes apache/skywalking#11605

Test plan

  • New plugin test tests/plugin/web/sw_threading/ — plain function with Thread, validates CrossThread ref in expected data
  • Existing Flask test (tests/plugin/web/sw_flask/) — uses inline @runnable, should still pass in CI
  • CI full matrix validation

🤖 Generated with Claude Code

…agation

Previously, @Runnable captured the snapshot at decoration time, which only
worked when the decorator was applied inline during an active request. When
used at module level (the natural Python pattern), the snapshot was None
because no trace context existed at import time.

The new sw_threading plugin patches Thread.__init__ to capture the snapshot
on the parent thread, and Thread.run to automatically create a local span
with CrossThread ref on the child thread. This makes cross-thread trace
propagation transparent without any decorator.

@Runnable is updated to read the snapshot from the Thread object at
execution time, so both module-level and inline usage now work correctly.
When sw_threading detects the target is @runnable-wrapped, it defers to
@Runnable for span creation.

Closes apache/skywalking#11605

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@wu-sheng wu-sheng added this to the 1.3.0 milestone Apr 12, 2026
@wu-sheng wu-sheng added enhancement New feature or request feature New feature plugin Plugin labels Apr 12, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@wu-sheng wu-sheng closed this Apr 12, 2026
@wu-sheng wu-sheng deleted the feat/threading-plugin branch April 12, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request feature New feature plugin Plugin

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Python agent’s @runnable does not behave as expected

1 participant