io.camunda.zeebe.process.test.engine.EngineStateMonitor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of zeebe-process-test-engine Show documentation
Show all versions of zeebe-process-test-engine Show documentation
Test engine for running process tests. This a minimized version of the real Zeebe engine and
should be used for testing purposes only.
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
* one or more contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright ownership.
* Licensed under the Zeebe Community License 1.1. You may not use this file
* except in compliance with the Zeebe Community License 1.1.
*/
package io.camunda.zeebe.process.test.engine;
import io.camunda.zeebe.logstreams.storage.LogStorage;
import io.camunda.zeebe.stream.impl.StreamProcessor;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Monitor that monitors whether the engine is busy or in idle state. Busy state is a state in which
* the engine is actively writing new events to the logstream. Idle state is a state in which the
* process engine makes no progress and is waiting for new commands or events to trigger
* The busy state callbacks are notified immediately as soon as a new commit is registered in the
* log storage.
* The idle state callbacks are notified when the idle state has lasted
* PERIOD * NOTIFICATION_THRESHOLD
ms
*/
final class EngineStateMonitor implements LogStorage.CommitListener {
private static final Logger LOG = LoggerFactory.getLogger(EngineStateMonitor.class);
private static final int GRACE_PERIOD_MS = 50;
private static final int NOTIFICATION_THRESHOLD = 2;
private static final Timer TIMER = new Timer();
private final List idleCallbacks = new ArrayList<>();
private final List processingCallbacks = new ArrayList<>();
private final StreamProcessor streamProcessor;
private volatile TimerTask stateNotifier;
EngineStateMonitor(final InMemoryLogStorage logStorage, final StreamProcessor streamProcessor) {
logStorage.addCommitListener(this);
this.streamProcessor = streamProcessor;
}
public void addOnIdleCallback(final Runnable callback) {
synchronized (idleCallbacks) {
idleCallbacks.add(callback);
}
scheduleStateNotification();
}
public void addOnProcessingCallback(final Runnable callback) {
synchronized (processingCallbacks) {
processingCallbacks.add(callback);
}
scheduleStateNotification();
}
private synchronized void scheduleStateNotification() {
if (stateNotifier != null) {
// cancel last task
stateNotifier.cancel();
TIMER.purge();
}
stateNotifier = createStateNotifier();
TIMER.scheduleAtFixedRate(stateNotifier, GRACE_PERIOD_MS, GRACE_PERIOD_MS);
}
private boolean isInIdleState() {
try {
return streamProcessor.hasProcessingReachedTheEnd().join();
} catch (final Exception e) {
LOG.debug("Exception occurred while checking idle state", e);
// A ExecutionException may be thrown here if the actor is already closed. For some mysterious
// reason this causes the testcontainer to terminate, which is why we need to catch it.
// We cannot catch the ExecutionException itself, as Zeebe turns this into an unchecked
// exception. Because of this we need to catch Exception instead.
return streamProcessor.isActorClosed();
}
}
@Override
public void onCommit() {
notifyProcessingCallbacks(); // notify processing callbacks immediately
if (!idleCallbacks.isEmpty() || !processingCallbacks.isEmpty()) {
scheduleStateNotification();
}
}
private void notifyIdleCallbacks() {
synchronized (idleCallbacks) {
idleCallbacks.forEach(Runnable::run);
idleCallbacks.clear();
}
}
private void notifyProcessingCallbacks() {
synchronized (processingCallbacks) {
processingCallbacks.forEach(Runnable::run);
processingCallbacks.clear();
}
}
private TimerTask createStateNotifier() {
return new TimerTask() {
private int idleStateReachedCounter = 0;
@Override
public void run() {
if (!idleCallbacks.isEmpty() || !processingCallbacks.isEmpty()) {
if (isInIdleState()) {
idleStateReachedCounter++;
if (idleStateReachedCounter >= NOTIFICATION_THRESHOLD) {
notifyIdleCallbacks();
}
} else {
idleStateReachedCounter = 0;
notifyProcessingCallbacks();
}
}
if (idleCallbacks.isEmpty() && processingCallbacks.isEmpty()) {
cancel();
}
}
};
}
}