aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.47k stars 3.82k forks source link

(aws-events): circular dependency error when providing cross-stack Queue to Rule.addTarget() #30530

Open dmeehan1968 opened 2 months ago

dmeehan1968 commented 2 months ago

Describe the bug

Reopens to #18821

Original bug report reproduced below

Given one stack that creates a Queue When I consume that Queue in another stack using Rule.addTarget(queue) Then I get a circular dependency error

Expected Behavior

No circular dependency error.

Current Behavior

Error: 'QueueProducer' depends on 'QueueConsumer' (QueueProducer -> QueueConsumer/MyRule/Resource.Arn). Adding this dependency (QueueConsumer -> QueueProducer/MainQueue/Resource.Arn) would create a cyclic reference.

Reproduction Steps

import { App, Stack } from "aws-cdk-lib";
import {Queue} from "aws-cdk-lib/aws-sqs";
import { EventBus, Rule } from "aws-cdk-lib/aws-events";
import { SqsQueue } from "aws-cdk-lib/aws-events-targets";

const app = new App();

// Stack A
const queueProducerStack = new Stack(app, "QueueProducer");
const mainQueue = new Queue(queueProducerStack, "MainQueue");

// Stack B
const queueConsumerStack = new Stack(app, "QueueConsumer");
const eventBus = new EventBus(queueConsumerStack, "EventBus");
const eventRule = new Rule(queueConsumerStack, "MyRule", {
  eventBus,
  eventPattern: {
    account: [Stack.of(queueConsumerStack).account],
  },
});
const sqsQueue = new SqsQueue(mainQueue);
eventRule.addTarget(sqsQueue);

Possible Solution

No response

Additional Information/Context

Workaround: Use the queueArn to create a cross-stack reference, e.g.

Queue.fromQueueArn(mainQueue.queueArn)

credit

CDK CLI Version

2.145.0

Framework Version

No response

Node.js Version

18.18.1

OS

macOS 14.5

Language

TypeScript

Language Version

5.4.5

Other information

In my own encounter, its interesting to note that the error message seems to mention another queue, not the one that I'm actually referencing (and yes, I checked to make sure these were discrete and correct).

Code:

    rule.addTarget(new SqsQueue(state.payloadDecoder.queue))

Error:

Error: 'Prod/L8-state' depends on 'Prod/L8' (Prod/L8-state -> Prod/L8/Ingest/Decoder/Rule/Resource.Arn). Adding this dependency (Prod/L8 -> Prod/L8-state/NormalisedUplinks/Queue/Resource.Arn) would create a cyclic reference.

The payload decoder rule is receiving a reference to the payloadDecoder.queue, but the error references the normalisedUplinks.queue, which is also created in the other stack (I have a state stack, and a compute stack). Compute stack creates the rule, and references the queue in the state stack.

khushail commented 2 months ago

Hi @dmeehan1968 , thanks for reporting this. I see this past issue is still occurring , marking current one as P2 as there is a workaround available as mentioned.

dmeehan1968 commented 2 months ago

@khushail It appears that the workaround causes the stateful stack to have CfnOutput's associated with the source queue, held by the stateless stack but exported from the stateful stack. This causes a deadlock that prevents changes to the queue, because a reference is held by the stateless stack.

I think the workarond to the lock is to create a second queue, pass that to the stateless stack in order to free the attribute reference, and then on a subsequent deploy to remove the previous stack.

I think this is a common problem with cross stack resources, I've experienced similar with DynamoDB tables, and thus might affect SQS Queues as well. I'm not sure whether there is best practice guidance on how to avoid this beyond what I mention above.

Screenshot 2024-06-12 at 11 24 10

Medium Article describing the process of releasing cross stack resources