first-fix

This commit is contained in:
Alexander Abakumov committed 2024-06-23 23:04:49 +03:00
1 parent 6d87c1edc8
commit 565d9d673a
6284 files changed
+820267 -76

No files matched your search

+79
View File
@@ -0,0 +1,79 @@
<!--
Copyright © 2016-2023 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>0.6.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>
<artifactId>actor</artifactId>
<packaging>jar</packaging>
<name>Thingsboard Actor system</name>
<url>https://thingsboard.io</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/../..</main.dir>
</properties>
<dependencies>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>util</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>message</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,34 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Getter;
public abstract class AbstractTbActor implements TbActor {
@Getter
protected TbActorCtx ctx;
@Override
public void init(TbActorCtx ctx) throws TbActorException {
this.ctx = ctx;
}
@Override
public TbActorRef getActorRef() {
return ctx;
}
}
@@ -0,0 +1,215 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.msg.TbActorMsg;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Slf4j
@Data
public class DefaultTbActorSystem implements TbActorSystem {
private final ConcurrentMap<String, Dispatcher> dispatchers = new ConcurrentHashMap<>();
private final ConcurrentMap<TbActorId, TbActorMailbox> actors = new ConcurrentHashMap<>();
private final ConcurrentMap<TbActorId, ReentrantLock> actorCreationLocks = new ConcurrentHashMap<>();
private final ConcurrentMap<TbActorId, Set<TbActorId>> parentChildMap = new ConcurrentHashMap<>();
@Getter
private final TbActorSystemSettings settings;
@Getter
private final ScheduledExecutorService scheduler;
public DefaultTbActorSystem(TbActorSystemSettings settings) {
this.settings = settings;
this.scheduler = Executors.newScheduledThreadPool(settings.getSchedulerPoolSize(), ThingsBoardThreadFactory.forName("actor-system-scheduler"));
}
@Override
public void createDispatcher(String dispatcherId, ExecutorService executor) {
Dispatcher current = dispatchers.putIfAbsent(dispatcherId, new Dispatcher(dispatcherId, executor));
if (current != null) {
throw new RuntimeException("Dispatcher with id [" + dispatcherId + "] is already registered!");
}
}
@Override
public void destroyDispatcher(String dispatcherId) {
Dispatcher dispatcher = dispatchers.remove(dispatcherId);
if (dispatcher != null) {
dispatcher.getExecutor().shutdownNow();
} else {
throw new RuntimeException("Dispatcher with id [" + dispatcherId + "] is not registered!");
}
}
@Override
public TbActorRef getActor(TbActorId actorId) {
return actors.get(actorId);
}
@Override
public TbActorRef createRootActor(String dispatcherId, TbActorCreator creator) {
return createActor(dispatcherId, creator, null);
}
@Override
public TbActorRef createChildActor(String dispatcherId, TbActorCreator creator, TbActorId parent) {
return createActor(dispatcherId, creator, parent);
}
private TbActorRef createActor(String dispatcherId, TbActorCreator creator, TbActorId parent) {
Dispatcher dispatcher = dispatchers.get(dispatcherId);
if (dispatcher == null) {
log.warn("Dispatcher with id [{}] is not registered!", dispatcherId);
throw new RuntimeException("Dispatcher with id [" + dispatcherId + "] is not registered!");
}
TbActorId actorId = creator.createActorId();
TbActorMailbox actorMailbox = actors.get(actorId);
if (actorMailbox != null) {
log.debug("Actor with id [{}] is already registered!", actorId);
} else {
Lock actorCreationLock = actorCreationLocks.computeIfAbsent(actorId, id -> new ReentrantLock());
actorCreationLock.lock();
try {
actorMailbox = actors.get(actorId);
if (actorMailbox == null) {
log.debug("Creating actor with id [{}]!", actorId);
TbActor actor = creator.createActor();
TbActorRef parentRef = null;
if (parent != null) {
parentRef = getActor(parent);
if (parentRef == null) {
throw new TbActorNotRegisteredException(parent, "Parent Actor with id [" + parent + "] is not registered!");
}
}
TbActorMailbox mailbox = new TbActorMailbox(this, settings, actorId, parentRef, actor, dispatcher);
actors.put(actorId, mailbox);
mailbox.initActor();
actorMailbox = mailbox;
if (parent != null) {
parentChildMap.computeIfAbsent(parent, id -> ConcurrentHashMap.newKeySet()).add(actorId);
}
} else {
log.debug("Actor with id [{}] is already registered!", actorId);
}
} finally {
actorCreationLock.unlock();
actorCreationLocks.remove(actorId);
}
}
return actorMailbox;
}
@Override
public void tellWithHighPriority(TbActorId target, TbActorMsg actorMsg) {
tell(target, actorMsg, true);
}
@Override
public void tell(TbActorId target, TbActorMsg actorMsg) {
tell(target, actorMsg, false);
}
private void tell(TbActorId target, TbActorMsg actorMsg, boolean highPriority) {
TbActorMailbox mailbox = actors.get(target);
if (mailbox == null) {
throw new TbActorNotRegisteredException(target, "Actor with id [" + target + "] is not registered!");
}
if (highPriority) {
mailbox.tellWithHighPriority(actorMsg);
} else {
mailbox.tell(actorMsg);
}
}
@Override
public void broadcastToChildren(TbActorId parent, TbActorMsg msg) {
broadcastToChildren(parent, id -> true, msg);
}
@Override
public void broadcastToChildren(TbActorId parent, Predicate<TbActorId> childFilter, TbActorMsg msg) {
Set<TbActorId> children = parentChildMap.get(parent);
if (children != null) {
children.stream().filter(childFilter).forEach(id -> tell(id, msg));
}
}
@Override
public List<TbActorId> filterChildren(TbActorId parent, Predicate<TbActorId> childFilter) {
Set<TbActorId> children = parentChildMap.get(parent);
if (children != null) {
return children.stream().filter(childFilter).collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
@Override
public void stop(TbActorRef actorRef) {
stop(actorRef.getActorId());
}
@Override
public void stop(TbActorId actorId) {
Set<TbActorId> children = parentChildMap.remove(actorId);
if (children != null) {
for (TbActorId child : children) {
stop(child);
}
}
TbActorMailbox mailbox = actors.remove(actorId);
if (mailbox != null) {
mailbox.destroy(null);
}
}
@Override
public void stop() {
dispatchers.values().forEach(dispatcher -> {
dispatcher.getExecutor().shutdown();
try {
dispatcher.getExecutor().awaitTermination(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.warn("[{}] Failed to stop dispatcher", dispatcher.getDispatcherId(), e);
}
});
if (scheduler != null) {
scheduler.shutdownNow();
}
actors.clear();
}
}
@@ -0,0 +1,28 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Data;
import java.util.concurrent.ExecutorService;
@Data
class Dispatcher {
private final String dispatcherId;
private final ExecutorService executor;
}
@@ -0,0 +1,45 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Getter;
import lombok.ToString;
@ToString
public class InitFailureStrategy {
@Getter
private boolean stop;
@Getter
private long retryDelay;
private InitFailureStrategy(boolean stop, long retryDelay) {
this.stop = stop;
this.retryDelay = retryDelay;
}
public static InitFailureStrategy retryImmediately() {
return new InitFailureStrategy(false, 0);
}
public static InitFailureStrategy retryWithDelay(long ms) {
return new InitFailureStrategy(false, ms);
}
public static InitFailureStrategy stop() {
return new InitFailureStrategy(true, 0);
}
}
@@ -0,0 +1,44 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
public interface JsInvokeStats {
default void incrementRequests() {
incrementRequests(1);
}
void incrementRequests(int amount);
default void incrementResponses() {
incrementResponses(1);
}
void incrementResponses(int amount);
default void incrementFailures() {
incrementFailures(1);
}
void incrementFailures(int amount);
int getRequests();
int getResponses();
int getFailures();
void reset();
}
@@ -0,0 +1,38 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Getter;
import lombok.ToString;
@ToString
public class ProcessFailureStrategy {
@Getter
private boolean stop;
private ProcessFailureStrategy(boolean stop) {
this.stop = stop;
}
public static ProcessFailureStrategy stop() {
return new ProcessFailureStrategy(true);
}
public static ProcessFailureStrategy resume() {
return new ProcessFailureStrategy(false);
}
}
@@ -0,0 +1,44 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbActorStopReason;
public interface TbActor {
boolean process(TbActorMsg msg);
TbActorRef getActorRef();
default void init(TbActorCtx ctx) throws TbActorException {
}
default void destroy(TbActorStopReason stopReason, Throwable cause) throws TbActorException {
}
default InitFailureStrategy onInitFailure(int attempt, Throwable t) {
return InitFailureStrategy.retryWithDelay(5000L * attempt);
}
default ProcessFailureStrategy onProcessFailure(Throwable t) {
if (t instanceof Error) {
return ProcessFailureStrategy.stop();
} else {
return ProcessFailureStrategy.resume();
}
}
}
@@ -0,0 +1,24 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
public interface TbActorCreator {
TbActorId createActorId();
TbActor createActor();
}
@@ -0,0 +1,44 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.msg.TbActorMsg;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Supplier;
public interface TbActorCtx extends TbActorRef {
TbActorId getSelf();
TbActorRef getParentRef();
void tell(TbActorId target, TbActorMsg msg);
void stop(TbActorId target);
TbActorRef getOrCreateChildActor(TbActorId actorId, Supplier<String> dispatcher, Supplier<TbActorCreator> creator);
void broadcastToChildren(TbActorMsg msg);
void broadcastToChildrenByType(TbActorMsg msg, EntityType entityType);
void broadcastToChildren(TbActorMsg msg, Predicate<TbActorId> childFilter);
List<TbActorId> filterChildren(Predicate<TbActorId> childFilter);
}
@@ -0,0 +1,25 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
public class TbActorException extends Exception {
private static final long serialVersionUID = 8209771144711980882L;
public TbActorException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,30 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import org.thingsboard.server.common.data.EntityType;
public interface TbActorId {
/**
* Returns entity type of the actor.
* May return null if the actor does not belong to any entity.
* This method is added for performance optimization.
*
*/
EntityType getEntityType();
}
@@ -0,0 +1,256 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorError;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbActorStopReason;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.function.Supplier;
@Slf4j
@Data
public final class TbActorMailbox implements TbActorCtx {
private static final boolean HIGH_PRIORITY = true;
private static final boolean NORMAL_PRIORITY = false;
private static final boolean FREE = false;
private static final boolean BUSY = true;
private static final boolean NOT_READY = false;
private static final boolean READY = true;
private final TbActorSystem system;
private final TbActorSystemSettings settings;
private final TbActorId selfId;
private final TbActorRef parentRef;
private final TbActor actor;
private final Dispatcher dispatcher;
private final ConcurrentLinkedQueue<TbActorMsg> highPriorityMsgs = new ConcurrentLinkedQueue<>();
private final ConcurrentLinkedQueue<TbActorMsg> normalPriorityMsgs = new ConcurrentLinkedQueue<>();
private final AtomicBoolean busy = new AtomicBoolean(FREE);
private final AtomicBoolean ready = new AtomicBoolean(NOT_READY);
private final AtomicBoolean destroyInProgress = new AtomicBoolean();
private volatile TbActorStopReason stopReason;
public void initActor() {
dispatcher.getExecutor().execute(() -> tryInit(1));
}
private void tryInit(int attempt) {
try {
log.debug("[{}] Trying to init actor, attempt: {}", selfId, attempt);
if (!destroyInProgress.get()) {
actor.init(this);
if (!destroyInProgress.get()) {
ready.set(READY);
tryProcessQueue(false);
}
}
} catch (Throwable t) {
InitFailureStrategy strategy;
int attemptIdx = attempt + 1;
if (isUnrecoverable(t)) {
strategy = InitFailureStrategy.stop();
} else {
log.debug("[{}] Failed to init actor, attempt: {}", selfId, attempt, t);
strategy = actor.onInitFailure(attempt, t);
}
if (strategy.isStop() || (settings.getMaxActorInitAttempts() > 0 && attemptIdx > settings.getMaxActorInitAttempts())) {
log.info("[{}] Failed to init actor, attempt {}, going to stop attempts.", selfId, attempt, t);
stopReason = TbActorStopReason.INIT_FAILED;
destroy(t.getCause());
} else if (strategy.getRetryDelay() > 0) {
log.info("[{}] Failed to init actor, attempt {}, going to retry in attempts in {}ms", selfId, attempt, strategy.getRetryDelay());
log.debug("[{}] Error", selfId, t);
system.getScheduler().schedule(() -> dispatcher.getExecutor().execute(() -> tryInit(attemptIdx)), strategy.getRetryDelay(), TimeUnit.MILLISECONDS);
} else {
log.info("[{}] Failed to init actor, attempt {}, going to retry immediately", selfId, attempt);
log.debug("[{}] Error", selfId, t);
dispatcher.getExecutor().execute(() -> tryInit(attemptIdx));
}
}
}
private static boolean isUnrecoverable(Throwable t) {
if (t instanceof TbActorException && t.getCause() != null) {
t = t.getCause();
}
return t instanceof TbActorError && ((TbActorError) t).isUnrecoverable();
}
private void enqueue(TbActorMsg msg, boolean highPriority) {
if (!destroyInProgress.get()) {
if (highPriority) {
highPriorityMsgs.add(msg);
} else {
normalPriorityMsgs.add(msg);
}
tryProcessQueue(true);
} else {
if (highPriority && msg.getMsgType().equals(MsgType.RULE_NODE_UPDATED_MSG)) {
synchronized (this) {
if (stopReason == TbActorStopReason.INIT_FAILED) {
destroyInProgress.set(false);
stopReason = null;
initActor();
} else {
msg.onTbActorStopped(stopReason);
}
}
} else {
msg.onTbActorStopped(stopReason);
}
}
}
private void tryProcessQueue(boolean newMsg) {
if (ready.get() == READY) {
if (newMsg || !highPriorityMsgs.isEmpty() || !normalPriorityMsgs.isEmpty()) {
if (busy.compareAndSet(FREE, BUSY)) {
dispatcher.getExecutor().execute(this::processMailbox);
} else {
log.trace("[{}] MessageBox is busy, new msg: {}", selfId, newMsg);
}
} else {
log.trace("[{}] MessageBox is empty, new msg: {}", selfId, newMsg);
}
} else {
log.trace("[{}] MessageBox is not ready, new msg: {}", selfId, newMsg);
}
}
private void processMailbox() {
boolean noMoreElements = false;
for (int i = 0; i < settings.getActorThroughput(); i++) {
TbActorMsg msg = highPriorityMsgs.poll();
if (msg == null) {
msg = normalPriorityMsgs.poll();
}
if (msg != null) {
try {
log.debug("[{}] Going to process message: {}", selfId, msg);
actor.process(msg);
} catch (TbRuleNodeUpdateException updateException) {
stopReason = TbActorStopReason.INIT_FAILED;
destroy(updateException.getCause());
} catch (Throwable t) {
log.debug("[{}] Failed to process message: {}", selfId, msg, t);
ProcessFailureStrategy strategy = actor.onProcessFailure(t);
if (strategy.isStop()) {
system.stop(selfId);
}
}
} else {
noMoreElements = true;
break;
}
}
if (noMoreElements) {
busy.set(FREE);
dispatcher.getExecutor().execute(() -> tryProcessQueue(false));
} else {
dispatcher.getExecutor().execute(this::processMailbox);
}
}
@Override
public TbActorId getSelf() {
return selfId;
}
@Override
public void tell(TbActorId target, TbActorMsg actorMsg) {
system.tell(target, actorMsg);
}
@Override
public void broadcastToChildren(TbActorMsg msg) {
system.broadcastToChildren(selfId, msg);
}
@Override
public void broadcastToChildrenByType(TbActorMsg msg, EntityType entityType) {
broadcastToChildren(msg, actorId -> entityType.equals(actorId.getEntityType()));
}
@Override
public void broadcastToChildren(TbActorMsg msg, Predicate<TbActorId> childFilter) {
system.broadcastToChildren(selfId, childFilter, msg);
}
@Override
public List<TbActorId> filterChildren(Predicate<TbActorId> childFilter) {
return system.filterChildren(selfId, childFilter);
}
@Override
public void stop(TbActorId target) {
system.stop(target);
}
@Override
public TbActorRef getOrCreateChildActor(TbActorId actorId, Supplier<String> dispatcher, Supplier<TbActorCreator> creator) {
TbActorRef actorRef = system.getActor(actorId);
if (actorRef == null) {
return system.createChildActor(dispatcher.get(), creator.get(), selfId);
} else {
return actorRef;
}
}
public void destroy(Throwable cause) {
if (stopReason == null) {
stopReason = TbActorStopReason.STOPPED;
}
destroyInProgress.set(true);
dispatcher.getExecutor().execute(() -> {
try {
ready.set(NOT_READY);
actor.destroy(stopReason, cause);
highPriorityMsgs.forEach(msg -> msg.onTbActorStopped(stopReason));
normalPriorityMsgs.forEach(msg -> msg.onTbActorStopped(stopReason));
} catch (Throwable t) {
log.warn("[{}] Failed to destroy actor: {}", selfId, t);
}
});
}
@Override
public TbActorId getActorId() {
return selfId;
}
@Override
public void tell(TbActorMsg actorMsg) {
enqueue(actorMsg, NORMAL_PRIORITY);
}
@Override
public void tellWithHighPriority(TbActorMsg actorMsg) {
enqueue(actorMsg, HIGH_PRIORITY);
}
}
@@ -0,0 +1,29 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Getter;
public class TbActorNotRegisteredException extends RuntimeException {
@Getter
private TbActorId target;
public TbActorNotRegisteredException(TbActorId target, String message) {
super(message);
this.target = target;
}
}
@@ -0,0 +1,28 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import org.thingsboard.server.common.msg.TbActorMsg;
public interface TbActorRef {
TbActorId getActorId();
void tell(TbActorMsg actorMsg);
void tellWithHighPriority(TbActorMsg actorMsg);
}
@@ -0,0 +1,54 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import org.thingsboard.server.common.msg.TbActorMsg;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Predicate;
public interface TbActorSystem {
ScheduledExecutorService getScheduler();
void createDispatcher(String dispatcherId, ExecutorService executor);
void destroyDispatcher(String dispatcherId);
TbActorRef getActor(TbActorId actorId);
TbActorRef createRootActor(String dispatcherId, TbActorCreator creator);
TbActorRef createChildActor(String dispatcherId, TbActorCreator creator, TbActorId parent);
void tell(TbActorId target, TbActorMsg actorMsg);
void tellWithHighPriority(TbActorId target, TbActorMsg actorMsg);
void stop(TbActorRef actorRef);
void stop(TbActorId actorId);
void stop();
void broadcastToChildren(TbActorId parent, TbActorMsg msg);
void broadcastToChildren(TbActorId parent, Predicate<TbActorId> childFilter, TbActorMsg msg);
List<TbActorId> filterChildren(TbActorId parent, Predicate<TbActorId> childFilter);
}
@@ -0,0 +1,27 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Data;
@Data
public class TbActorSystemSettings {
private final int actorThroughput;
private final int schedulerPoolSize;
private final int maxActorInitAttempts;
}
@@ -0,0 +1,55 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Getter;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.Objects;
public class TbEntityActorId implements TbActorId {
@Getter
private final EntityId entityId;
public TbEntityActorId(EntityId entityId) {
this.entityId = entityId;
}
@Override
public String toString() {
return entityId.getEntityType() + "|" + entityId.getId();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TbEntityActorId that = (TbEntityActorId) o;
return entityId.equals(that.entityId);
}
@Override
public int hashCode() {
return Objects.hash(entityId);
}
@Override
public EntityType getEntityType() {
return entityId.getEntityType();
}
}
@@ -0,0 +1,26 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
public class TbRuleNodeUpdateException extends RuntimeException {
private static final long serialVersionUID = 8209771144711980882L;
public TbRuleNodeUpdateException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,52 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import org.thingsboard.server.common.data.EntityType;
import java.util.Objects;
public class TbStringActorId implements TbActorId {
private final String id;
public TbStringActorId(String id) {
this.id = id;
}
@Override
public String toString() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TbStringActorId that = (TbStringActorId) o;
return id.equals(that.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public EntityType getEntityType() {
return null;
}
}
@@ -0,0 +1,259 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.id.DeviceId;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@Slf4j
@RunWith(MockitoJUnitRunner.class)
public class ActorSystemTest {
public static final String ROOT_DISPATCHER = "root-dispatcher";
private static final int _100K = 100 * 1024;
public static final int TIMEOUT_AWAIT_MAX_SEC = 100;
private volatile TbActorSystem actorSystem;
private volatile ExecutorService submitPool;
private ExecutorService executor;
private int parallelism;
@Before
public void initActorSystem() {
int cores = Runtime.getRuntime().availableProcessors();
parallelism = Math.max(2, cores / 2);
TbActorSystemSettings settings = new TbActorSystemSettings(5, parallelism, 42);
actorSystem = new DefaultTbActorSystem(settings);
submitPool = Executors.newFixedThreadPool(parallelism, ThingsBoardThreadFactory.forName(getClass().getSimpleName() + "-submit-test-scope")); //order guaranteed
}
@After
public void shutdownActorSystem() {
actorSystem.stop();
submitPool.shutdownNow();
if (executor != null) {
executor.shutdownNow();
}
}
@Test
public void test1actorsAnd100KMessages() throws InterruptedException {
executor = ThingsBoardExecutors.newWorkStealingPool(parallelism, getClass());
actorSystem.createDispatcher(ROOT_DISPATCHER, executor);
testActorsAndMessages(1, _100K, 1);
}
@Test
public void test10actorsAnd100KMessages() throws InterruptedException {
executor = ThingsBoardExecutors.newWorkStealingPool(parallelism, getClass());
actorSystem.createDispatcher(ROOT_DISPATCHER, executor);
testActorsAndMessages(10, _100K, 1);
}
@Test
public void test100KActorsAnd1Messages5timesSingleThread() throws InterruptedException {
executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName(getClass().getSimpleName()));
actorSystem.createDispatcher(ROOT_DISPATCHER, executor);
testActorsAndMessages(_100K, 1, 5);
}
@Test
public void test100KActorsAnd1Messages5times() throws InterruptedException {
executor = ThingsBoardExecutors.newWorkStealingPool(parallelism, getClass());
actorSystem.createDispatcher(ROOT_DISPATCHER, executor);
testActorsAndMessages(_100K, 1, 5);
}
@Test
public void test100KActorsAnd10Messages() throws InterruptedException {
executor = ThingsBoardExecutors.newWorkStealingPool(parallelism, getClass());
actorSystem.createDispatcher(ROOT_DISPATCHER, executor);
testActorsAndMessages(_100K, 10, 1);
}
@Test
public void test1KActorsAnd1KMessages() throws InterruptedException {
executor = ThingsBoardExecutors.newWorkStealingPool(parallelism, getClass());
actorSystem.createDispatcher(ROOT_DISPATCHER, executor);
testActorsAndMessages(1000, 1000, 10);
}
@Test
public void testNoMessagesAfterDestroy() throws InterruptedException {
executor = ThingsBoardExecutors.newWorkStealingPool(parallelism, getClass());
actorSystem.createDispatcher(ROOT_DISPATCHER, executor);
ActorTestCtx testCtx1 = getActorTestCtx(1);
ActorTestCtx testCtx2 = getActorTestCtx(1);
TbActorRef actorId1 = actorSystem.createRootActor(ROOT_DISPATCHER, new SlowInitActor.SlowInitActorCreator(
new TbEntityActorId(new DeviceId(UUID.randomUUID())), testCtx1));
TbActorRef actorId2 = actorSystem.createRootActor(ROOT_DISPATCHER, new SlowInitActor.SlowInitActorCreator(
new TbEntityActorId(new DeviceId(UUID.randomUUID())), testCtx2));
actorId1.tell(new IntTbActorMsg(42));
actorId2.tell(new IntTbActorMsg(42));
actorSystem.stop(actorId1);
Assert.assertTrue(testCtx2.getLatch().await(1, TimeUnit.SECONDS));
Assert.assertFalse(testCtx1.getLatch().await(1, TimeUnit.SECONDS));
}
@Test
public void testOneActorCreated() throws InterruptedException {
executor = ThingsBoardExecutors.newWorkStealingPool(parallelism, getClass());
actorSystem.createDispatcher(ROOT_DISPATCHER, executor);
ActorTestCtx testCtx1 = getActorTestCtx(1);
ActorTestCtx testCtx2 = getActorTestCtx(1);
TbActorId actorId = new TbEntityActorId(new DeviceId(UUID.randomUUID()));
final CountDownLatch initLatch = new CountDownLatch(1);
final CountDownLatch actorsReadyLatch = new CountDownLatch(2);
submitPool.submit(() -> {
actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx1, initLatch));
actorsReadyLatch.countDown();
});
submitPool.submit(() -> {
actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx2, initLatch));
actorsReadyLatch.countDown();
});
initLatch.countDown(); //replacement for Thread.wait(500) in the SlowCreateActorCreator
Assert.assertTrue(actorsReadyLatch.await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS));
actorSystem.tell(actorId, new IntTbActorMsg(42));
Assert.assertTrue(testCtx1.getLatch().await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS));
Assert.assertFalse(testCtx2.getLatch().await(1, TimeUnit.SECONDS));
}
@Test
public void testActorCreatorCalledOnce() throws InterruptedException {
executor = ThingsBoardExecutors.newWorkStealingPool(parallelism, getClass());
actorSystem.createDispatcher(ROOT_DISPATCHER, executor);
ActorTestCtx testCtx = getActorTestCtx(1);
TbActorId actorId = new TbEntityActorId(new DeviceId(UUID.randomUUID()));
final int actorsCount = 1000;
final CountDownLatch initLatch = new CountDownLatch(1);
final CountDownLatch actorsReadyLatch = new CountDownLatch(actorsCount);
for (int i = 0; i < actorsCount; i++) {
submitPool.submit(() -> {
actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx, initLatch));
actorsReadyLatch.countDown();
});
}
initLatch.countDown();
Assert.assertTrue(actorsReadyLatch.await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS));
actorSystem.tell(actorId, new IntTbActorMsg(42));
Assert.assertTrue(testCtx.getLatch().await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS));
//One for creation and one for message
Assert.assertEquals(2, testCtx.getInvocationCount().get());
}
@Test
public void testFailedInit() throws InterruptedException {
executor = ThingsBoardExecutors.newWorkStealingPool(parallelism, getClass());
actorSystem.createDispatcher(ROOT_DISPATCHER, executor);
ActorTestCtx testCtx1 = getActorTestCtx(1);
ActorTestCtx testCtx2 = getActorTestCtx(1);
TbActorRef actorId1 = actorSystem.createRootActor(ROOT_DISPATCHER, new FailedToInitActor.FailedToInitActorCreator(
new TbEntityActorId(new DeviceId(UUID.randomUUID())), testCtx1, 1, 3000));
TbActorRef actorId2 = actorSystem.createRootActor(ROOT_DISPATCHER, new FailedToInitActor.FailedToInitActorCreator(
new TbEntityActorId(new DeviceId(UUID.randomUUID())), testCtx2, 2, 1));
actorId1.tell(new IntTbActorMsg(42));
actorId2.tell(new IntTbActorMsg(42));
Assert.assertFalse(testCtx1.getLatch().await(2, TimeUnit.SECONDS));
Assert.assertTrue(testCtx2.getLatch().await(1, TimeUnit.SECONDS));
Assert.assertTrue(testCtx1.getLatch().await(3, TimeUnit.SECONDS));
}
public void testActorsAndMessages(int actorsCount, int msgNumber, int times) throws InterruptedException {
Random random = new Random();
int[] randomIntegers = new int[msgNumber];
long sumTmp = 0;
for (int i = 0; i < msgNumber; i++) {
int tmp = random.nextInt();
randomIntegers[i] = tmp;
sumTmp += tmp;
}
long expected = sumTmp;
List<ActorTestCtx> testCtxes = new ArrayList<>();
List<TbActorRef> actorRefs = new ArrayList<>();
for (int actorIdx = 0; actorIdx < actorsCount; actorIdx++) {
ActorTestCtx testCtx = getActorTestCtx(msgNumber);
actorRefs.add(actorSystem.createRootActor(ROOT_DISPATCHER, new TestRootActor.TestRootActorCreator(
new TbEntityActorId(new DeviceId(UUID.randomUUID())), testCtx)));
testCtxes.add(testCtx);
}
for (int t = 0; t < times; t++) {
long start = System.nanoTime();
for (int i = 0; i < msgNumber; i++) {
int tmp = randomIntegers[i];
submitPool.execute(() -> actorRefs.forEach(actorId -> actorId.tell(new IntTbActorMsg(tmp))));
}
log.info("Submitted all messages");
testCtxes.forEach(ctx -> {
try {
boolean success = ctx.getLatch().await(1, TimeUnit.MINUTES);
if (!success) {
log.warn("Failed: {}, {}", ctx.getActual().get(), ctx.getInvocationCount().get());
}
Assert.assertTrue(success);
Assert.assertEquals(expected, ctx.getActual().get());
Assert.assertEquals(msgNumber, ctx.getInvocationCount().get());
ctx.clear();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long duration = System.nanoTime() - start;
log.info("Time spend: {}ns ({} ms)", duration, TimeUnit.NANOSECONDS.toMillis(duration));
}
}
private ActorTestCtx getActorTestCtx(int i) {
CountDownLatch countDownLatch = new CountDownLatch(1);
AtomicLong actual = new AtomicLong();
AtomicInteger invocations = new AtomicInteger();
return new ActorTestCtx(countDownLatch, invocations, i, actual);
}
}
@@ -0,0 +1,39 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@Data
@AllArgsConstructor
public class ActorTestCtx {
private volatile CountDownLatch latch;
private final AtomicInteger invocationCount;
private final int expectedInvocationCount;
private final AtomicLong actual;
public void clear() {
latch = new CountDownLatch(1);
invocationCount.set(0);
actual.set(0L);
}
}
@@ -0,0 +1,72 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class FailedToInitActor extends TestRootActor {
int retryAttempts;
int retryDelay;
int attempts = 0;
public FailedToInitActor(TbActorId actorId, ActorTestCtx testCtx, int retryAttempts, int retryDelay) {
super(actorId, testCtx);
this.retryAttempts = retryAttempts;
this.retryDelay = retryDelay;
}
@Override
public void init(TbActorCtx ctx) throws TbActorException {
if (attempts < retryAttempts) {
attempts++;
throw new TbActorException("Test attempt", new RuntimeException());
} else {
super.init(ctx);
}
}
@Override
public InitFailureStrategy onInitFailure(int attempt, Throwable t) {
return InitFailureStrategy.retryWithDelay(retryDelay);
}
public static class FailedToInitActorCreator implements TbActorCreator {
private final TbActorId actorId;
private final ActorTestCtx testCtx;
private final int retryAttempts;
private final int retryDelay;
public FailedToInitActorCreator(TbActorId actorId, ActorTestCtx testCtx, int retryAttempts, int retryDelay) {
this.actorId = actorId;
this.testCtx = testCtx;
this.retryAttempts = retryAttempts;
this.retryDelay = retryDelay;
}
@Override
public TbActorId createActorId() {
return actorId;
}
@Override
public TbActor createActor() {
return new FailedToInitActor(actorId, testCtx, retryAttempts, retryDelay);
}
}
}
@@ -0,0 +1,35 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Getter;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
public class IntTbActorMsg implements TbActorMsg {
@Getter
private final int value;
public IntTbActorMsg(int value) {
this.value = value;
}
@Override
public MsgType getMsgType() {
return MsgType.QUEUE_TO_RULE_ENGINE_MSG;
}
}
@@ -0,0 +1,60 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@Slf4j
public class SlowCreateActor extends TestRootActor {
public static final int TIMEOUT_AWAIT_MAX_MS = 5000;
public SlowCreateActor(TbActorId actorId, ActorTestCtx testCtx, CountDownLatch initLatch) {
super(actorId, testCtx);
try {
initLatch.await(TIMEOUT_AWAIT_MAX_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
testCtx.getInvocationCount().incrementAndGet();
}
public static class SlowCreateActorCreator implements TbActorCreator {
private final TbActorId actorId;
private final ActorTestCtx testCtx;
private final CountDownLatch initLatch;
public SlowCreateActorCreator(TbActorId actorId, ActorTestCtx testCtx, CountDownLatch initLatch) {
this.actorId = actorId;
this.testCtx = testCtx;
this.initLatch = initLatch;
}
@Override
public TbActorId createActorId() {
return actorId;
}
@Override
public TbActor createActor() {
return new SlowCreateActor(actorId, testCtx, initLatch);
}
}
}
@@ -0,0 +1,57 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SlowInitActor extends TestRootActor {
public SlowInitActor(TbActorId actorId, ActorTestCtx testCtx) {
super(actorId, testCtx);
}
@Override
public void init(TbActorCtx ctx) throws TbActorException {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
super.init(ctx);
}
public static class SlowInitActorCreator implements TbActorCreator {
private final TbActorId actorId;
private final ActorTestCtx testCtx;
public SlowInitActorCreator(TbActorId actorId, ActorTestCtx testCtx) {
this.actorId = actorId;
this.testCtx = testCtx;
}
@Override
public TbActorId createActorId() {
return actorId;
}
@Override
public TbActor createActor() {
return new SlowInitActor(actorId, testCtx);
}
}
}
@@ -0,0 +1,88 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbActorStopReason;
@Slf4j
public class TestRootActor extends AbstractTbActor {
@Getter
private final TbActorId actorId;
@Getter
private final ActorTestCtx testCtx;
private boolean initialized;
private long sum;
private int count;
public TestRootActor(TbActorId actorId, ActorTestCtx testCtx) {
this.actorId = actorId;
this.testCtx = testCtx;
}
@Override
public void init(TbActorCtx ctx) throws TbActorException {
super.init(ctx);
initialized = true;
}
@Override
public boolean process(TbActorMsg msg) {
if (initialized) {
int value = ((IntTbActorMsg) msg).getValue();
sum += value;
count += 1;
if (count == testCtx.getExpectedInvocationCount()) {
testCtx.getActual().set(sum);
testCtx.getInvocationCount().addAndGet(count);
sum = 0;
count = 0;
testCtx.getLatch().countDown();
}
}
return true;
}
@Override
public void destroy(TbActorStopReason stopReason, Throwable cause) {
}
public static class TestRootActorCreator implements TbActorCreator {
private final TbActorId actorId;
private final ActorTestCtx testCtx;
public TestRootActorCreator(TbActorId actorId, ActorTestCtx testCtx) {
this.actorId = actorId;
this.testCtx = testCtx;
}
@Override
public TbActorId createActorId() {
return actorId;
}
@Override
public TbActor createActor() {
return new TestRootActor(actorId, testCtx);
}
}
}
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console"/>
</root>
</configuration>