Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions examples/cloud-deployment/scripts/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,22 @@ echo ""
echo "Deploying PostgreSQL..."
kubectl apply -f ../k8s/01-postgres.yaml
echo "Waiting for PostgreSQL to be ready..."

# Wait for pod to be created (StatefulSet takes time to create pod)
for i in {1..30}; do
if kubectl get pod -l app=postgres -n a2a-demo 2>/dev/null | grep -q postgres; then
echo "PostgreSQL pod found, waiting for ready state..."
break
fi
if [ $i -eq 30 ]; then
echo -e "${RED}ERROR: PostgreSQL pod not created after 30 seconds${NC}"
kubectl get statefulset -n a2a-demo
exit 1
fi
sleep 1
done

# Now wait for pod to be ready
kubectl wait --for=condition=Ready pod -l app=postgres -n a2a-demo --timeout=120s
echo -e "${GREEN}✓ PostgreSQL deployed${NC}"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,45 @@ public void deleteInfo(String taskId, String configId) {
taskId, configId);
}
}

@Transactional
@Override
public void switchKey(String oldTaskId, String newTaskId) {
LOGGER.debug("Switching PushNotificationConfigs from Task '{}' to Task '{}'", oldTaskId, newTaskId);

// Find all configs for the old task ID
TypedQuery<JpaPushNotificationConfig> query = em.createQuery(
"SELECT c FROM JpaPushNotificationConfig c WHERE c.id.taskId = :taskId",
JpaPushNotificationConfig.class);
query.setParameter("taskId", oldTaskId);
List<JpaPushNotificationConfig> configs = query.getResultList();

if (configs.isEmpty()) {
LOGGER.debug("No PushNotificationConfigs found for Task '{}', nothing to switch", oldTaskId);
return;
}

// For each config, create a new entity with the new task ID and remove the old one
for (JpaPushNotificationConfig oldConfig : configs) {
try {
// Create new config with new task ID
JpaPushNotificationConfig newConfig = JpaPushNotificationConfig.createFromConfig(
newTaskId, oldConfig.getConfig());

// Remove old config and persist new one
em.remove(oldConfig);
em.persist(newConfig);

LOGGER.debug("Switched PushNotificationConfig ID '{}' from Task '{}' to Task '{}'",
oldConfig.getId().getConfigId(), oldTaskId, newTaskId);
} catch (JsonProcessingException e) {
LOGGER.error("Failed to switch PushNotificationConfig ID '{}' from Task '{}' to Task '{}'",
oldConfig.getId().getConfigId(), oldTaskId, newTaskId, e);
throw new RuntimeException("Failed to switch PushNotificationConfig", e);
}
}

LOGGER.debug("Successfully switched {} PushNotificationConfigs from Task '{}' to Task '{}'",
configs.size(), oldTaskId, newTaskId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
import io.a2a.server.events.EventQueueFactory;
import io.a2a.server.events.EventQueueItem;
import io.a2a.server.events.InMemoryQueueManager;
import io.a2a.server.events.MainEventBus;
import io.a2a.server.events.QueueManager;
import io.a2a.server.tasks.TaskStateProvider;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -45,10 +47,12 @@ protected ReplicatedQueueManager() {
}

@Inject
public ReplicatedQueueManager(ReplicationStrategy replicationStrategy, TaskStateProvider taskStateProvider) {
public ReplicatedQueueManager(ReplicationStrategy replicationStrategy,
TaskStateProvider taskStateProvider,
MainEventBus mainEventBus) {
this.replicationStrategy = replicationStrategy;
this.taskStateProvider = taskStateProvider;
this.delegate = new InMemoryQueueManager(new ReplicatingEventQueueFactory(), taskStateProvider);
this.delegate = new InMemoryQueueManager(new ReplicatingEventQueueFactory(), taskStateProvider, mainEventBus);
}


Expand All @@ -57,6 +61,11 @@ public void add(String taskId, EventQueue queue) {
delegate.add(taskId, queue);
}

@Override
public void switchKey(String oldId, String newId) {
delegate.switchKey(oldId, newId);
}

@Override
public EventQueue get(String taskId) {
return delegate.get(taskId);
Expand All @@ -77,7 +86,12 @@ public void close(String taskId) {

@Override
public EventQueue createOrTap(String taskId) {
EventQueue queue = delegate.createOrTap(taskId);
return createOrTap(taskId, null);
}

@Override
public EventQueue createOrTap(String taskId, @Nullable String tempId) {
EventQueue queue = delegate.createOrTap(taskId, tempId);
return queue;
}

Expand All @@ -98,7 +112,9 @@ public void onReplicatedEvent(@Observes ReplicatedEventQueueItem replicatedEvent
}

// Get or create a ChildQueue for this task (creates MainQueue if it doesn't exist)
EventQueue childQueue = delegate.createOrTap(replicatedEvent.getTaskId());
// Replicated events should always have real task IDs (not temp IDs) because
// replication now happens AFTER TaskStore persistence in MainEventBusProcessor
EventQueue childQueue = delegate.createOrTap(replicatedEvent.getTaskId(), null);

try {
// Get the MainQueue to enqueue the replicated event item
Expand Down Expand Up @@ -152,12 +168,11 @@ public EventQueue.EventQueueBuilder builder(String taskId) {
// which sends the QueueClosedEvent after the database transaction commits.
// This ensures proper ordering and transactional guarantees.

// Return the builder with callbacks
return delegate.getEventQueueBuilder(taskId)
.taskId(taskId)
.hook(new ReplicationHook(taskId))
.addOnCloseCallback(delegate.getCleanupCallback(taskId))
.taskStateProvider(taskStateProvider);
// Call createBaseEventQueueBuilder() directly to avoid infinite recursion
// (getEventQueueBuilder() would delegate back to this factory, creating a loop)
// The base builder already includes: taskId, cleanup callback, taskStateProvider, mainEventBus
return delegate.createBaseEventQueueBuilder(taskId)
.hook(new ReplicationHook(taskId));
}
}

Expand Down
Loading
Loading