Skip to content

Add proxy_context for Interposed Execution Control #25

@kammce

Description

@kammce

Problem

Currently, when a coroutine awaits another operation, control flows directly to the bottom-most coroutine with no opportunity for the caller to interpose logic between suspension points. This makes it impossible to implement patterns like:

  • Timeouts (cancel operation after time limit)
  • Progress monitoring (check operation state periodically)
  • Cooperative cancellation (check stop conditions between steps)

Proposed Solution

Add proxy_context - a scoped, immovable context type that allows a supervisor coroutine to regain control between an operation's suspension points.

API

class proxy_context : public context {
public:
    explicit proxy_context(context& parent);
    
    // Immovable - must not escape supervisor's scope
    proxy_context(proxy_context&&) = delete;
    proxy_context& operator=(proxy_context&&) = delete;
    // Not copyable
    proxy_context(proxy_context const&) = delete;
    proxy_context& operator=(proxy_context const&) = delete;
};

Usage Example

template<typename T>
future<T> with_timeout(context& ctx, 
                       future<result> operation, 
                       std::chrono::milliseconds limit) 
{
    std::atomic<bool> timed_out{false};
    auto timer = arm_timer(limit, [&]{ 
        timed_out = true;
        ctx.unblock();
    });
    
    proxy_context proxy(ctx);
    auto op = operation(proxy);
    
    while (!op.done() && !timed_out) {
        op.resume();  // Run proxy operation
        
        if (!op.done() && !timed_out) {
            co_await std::suspend_always{};
        }
    }
    
    if (timed_out) {
        throw timeout_error{};
    }
    
    co_return op.result();
}

Note that the 2nd parameter is a future, but should be a generic callable. That
way the future can be called using the proxy context and not the original
context.

Key Properties

  1. Scoped lifetime - Supervising coroutine creates proxy_context on its stack frame, ensuring memory safety through RAII
  2. Execution interposition - Supervising coroutine regains control after each suspension point in the proxy operation
  3. State delegation - Blocking operations (io, sync, etc.) delegate to parent context for scheduler visibility
  4. Authority to intervene - Supervising coroutine can inspect state and cancel operation between execution segments
  5. Zero allocation overhead - Hot path (coroutine frame allocation) requires no branches or checks

Benefits

  • Enables timeout implementations without scheduler modifications
  • Generic primitive for monitoring/controlling subordinate operations
  • Foundation for future patterns: retry limits, resource bounds, progress tracking
  • Maintains "minimal prescription, maximum control" philosophy

Implementation Notes

  • proxy_context uses remaining stack space from parent context
  • All state transitions (block/unblock) redirect to parent (original) context
  • Scheduler only sees original context, not proxy contexts
  • Memory loan is scoped - automatically returned when proxy_context's frame
    exits

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions