Skip to content

fix: restore tenant context for error channel in multi-tenant setup#677

Merged
dgafka merged 2 commits into
mainfrom
multi-tenant-dead-letter
Jul 2, 2026
Merged

fix: restore tenant context for error channel in multi-tenant setup#677
dgafka merged 2 commits into
mainfrom
multi-tenant-dead-letter

Conversation

@dgafka

@dgafka dgafka commented Jul 2, 2026

Copy link
Copy Markdown
Member

Why is this change proposed?

Fixes #676. In a multi-tenant setup, when a polling consumer fails and the message is routed to a DBAL Dead Letter error channel, DbalDeadLetterHandler crashes with Lack of context about tenant in Message Headers. By the time the error-channel interceptor catches the exception, the interceptor chain has already unwound — the tenant was deactivated and the propagated-headers context popped — so HeaderBasedMultiTenantConnectionFactory::createContext() cannot resolve any connection. Failed messages can never reach the dead letter, and the consumer crashes instead of self-healing.

Description of Changes

  • ErrorChannelService now re-establishes the failed message's header context (via MessageHeadersPropagatorInterceptor::storeHeaders) around the send to the error channel, so the tenant header of the failed message is visible again to any tenant-aware component downstream
  • The fix is placed in ErrorChannelService (not in the DBAL dead letter handler) so custom error handlers and other integrations regain tenant context as well
  • Added integration test proving a failed message lands in the dead letter table of the tenant it belongs to, and not in other tenants' databases

No configuration changes are needed — existing multi-tenant setups start working:

final class Configuration
{
    #[ServiceContext]
    public function multiTenant(): MultiTenantConfiguration
    {
        return MultiTenantConfiguration::create(
            tenantHeaderName: 'tenant',
            tenantToConnectionMapping: [
                'tenant_a' => 'tenant_a_connection',
                'tenant_b' => 'tenant_b_connection',
            ],
        );
    }

    #[ServiceContext]
    public function errorHandling(): ServiceConfiguration
    {
        return ServiceConfiguration::createWithDefaults()
            ->withDefaultErrorChannel('dbal_dead_letter');
    }
}

When a handler for tenant_a fails, the message is now stored in ecotone_error_messages in tenant A's database.

sequenceDiagram
    participant Consumer as Polling Consumer
    participant Ack as AcknowledgeInterceptor
    participant Error as ErrorChannelInterceptor
    participant Headers as HeadersPropagator
    participant Handler
    participant DLQ as DbalDeadLetterHandler

    Consumer->>Ack: message (tenant: tenant_a)
    Ack->>Error: proceed
    Error->>Headers: proceed (push tenant header context)
    Headers->>Handler: handle
    Handler--xHeaders: exception (context popped on unwind)
    Headers--xError: exception
    Note over Error: NEW: restore failed message's<br/>header context around send
    Error->>DLQ: store failed message
    DLQ->>DLQ: resolve tenant_a connection ✔
    Error-->>Ack: handled
    Ack->>Ack: accept()
Loading

Pull Request Contribution Terms

  • I have read and agree to the contribution terms outlined in CONTRIBUTING.

@dgafka dgafka merged commit e60b934 into main Jul 2, 2026
8 checks passed
@dgafka dgafka deleted the multi-tenant-dead-letter branch July 2, 2026 16:44
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.

DbalDeadLetterHandler not able to access multi tenant connection on createContext

1 participant