I am just starting with Akka and have created a test application. In it I create a bunch of actors who create a scheduler to generate a heartbeat event. Upon another type of event, I cancel the scheduler with heartbeat.cancel();, but I'd like to restart it when another event occurs. If I recreate the scheduler I see that the memory consumption increases continuously.
The question then would be either how do I resume the scheduler or how do I dispose the scheduler properly.
This is the code for that Actor
public class Device extends UntypedActor {
enum CommunicationStatus{
OK,
FAIL,
UNKNOWN
}
private static class Heartbeat {
}
public final String deviceId;
private CommunicationStatus commStatus;
private Cancellable heartBeatScheduler;
public Device(String Id)
{
deviceId = Id;
commStatus = CommunicationStatus.UNKNOWN;
}
#Override
public void preStart() {
getContext().system().eventStream().subscribe(getSelf(), DeviceCommunicationStatusUpdated.class);
startHeartbeat();
}
#Override
public void postStop() {
stopHeartBeat();
}
private void startHeartbeat() {
LoggingAdapter log = Logging.getLogger(getContext().system(), this);
log.info("Starting heartbeat");
heartBeatScheduler = getContext().system().scheduler().
schedule(Duration.Zero(),
Duration.create(1, TimeUnit.SECONDS),
getContext().self(),
new Heartbeat(),
getContext().system().dispatcher(),
ActorRef.noSender());
}
private void stopHeartBeat() {
if(!heartBeatScheduler.isCancelled()) {
LoggingAdapter log = Logging.getLogger(getContext().system(), this);
log.info("Stopping heartbeat");
heartBeatScheduler.cancel();
}
}
public String getDeviceId() {
return deviceId;
}
public CommunicationStatus getCommunicationStatus(){
return commStatus;
}
#Override
public void onReceive(Object message) throws Exception {
LoggingAdapter log = Logging.getLogger(getContext().system(), this);
if(message instanceof Heartbeat){
log.info("Pum, pum");
}
else if (message instanceof DeviceCommunicationStatusUpdated){
DeviceCommunicationStatusUpdated event = (DeviceCommunicationStatusUpdated) message;
if(event.deviceId == this.deviceId){
log.info("Received communication status update. '{}' is now {}", deviceId, event.state);
this.commStatus =
event.state == DeviceCommunicationStatusUpdated.State.OK ?
CommunicationStatus.OK : CommunicationStatus.FAIL;
if(commStatus == CommunicationStatus.OK && heartBeatScheduler.isCancelled()){
startHeartbeat();
}
else {
stopHeartBeat();
}
}
}
else unhandled(message);
}
}
Finally there is no leak, it's just that I'm new to Java and was impatient with the garbage collection. In any case, I would like to know about the resetting / restarting of a scheduler.
Related
I have a scenario, which process armeria request, and dispatch some event to guava's EventBus. the problem is I loss the context while process the event in the EventBus handler.
I want to know is there any way to let the event processor access ServiceRequestContext.
class EventListener {
#Subscribe
public void process(SomeCustomizedClass event) {
final ServiceRequestContext context = ServiceRequestContext.currentOrNull();
log.info("process ServiceRequestContext context={}", context);
}
}
register the event handler.
EventBus eventBus = new AsyncEventBus(ThreadPoolTaskExecutor());
eventBus.register(new EventListener());
here is my Armeria service
#Slf4j
public class NameAuthRestApi {
final NameAuthService nameAuthService;
#Post("/auth")
#ProducesJson
public Mono<RealNameAuthResp> auth(RealNameAuthReq req) {
return nameAuthService.auth(NameAuthConverter.CONVERTER.toDto(req))
.handle((result, sink) -> {
if (result.isSuccess()) {
// I post an event here, but the event process couldn't access the ServiceRequestContext
// that's would be the problem.
eventBus.post(new SomeCustomizedClass(result));
final RealNameAuthResp realNameAuthResp = new RealNameAuthResp();
realNameAuthResp.setTradeNo(result.getTradeNo());
realNameAuthResp.setSuccess(true);
sink.next(realNameAuthResp);
sink.complete();
} else {
sink.error(new SystemException(ErrorCode.API_ERROR, result.errors()));
}
});
}
}
You need to do:
public Mono<RealNameAuthResp> auth(ServiceRequestContxt ctx, RealNameAuthReq req) {
// Executed by an EventLoop 1.
// This thread has the ctx in its thread local.
return nameAuthService.auth(NameAuthConverter.CONVERTER.toDto(req))
.handle((result, sink) -> {
// Executed by another EventLoop 2.
// But this doens't.
try (SafeCloseable ignord = ctx.push()) {
if (result.isSuccess()) {
...
} else {
...
}
}
});
}
The problem is that the handle method is executed by another thread that does not have the ctx in its thread local. So, you should manually set the ctx.
You can achieve the same effect using xAsync method with the ctx.eventLoop():
public Mono<RealNameAuthResp> auth(ServiceRequestContxt ctx, RealNameAuthReq req) {
return nameAuthService.auth(NameAuthConverter.CONVERTER.toDto(req))
.handleAsync((result, sink) -> {
if (result.isSuccess()) {
...
} else {
...
}
}, ctx.eventLoop());
}
We have two ways to solve this:
First, use the executor which has the ctx:
ctx.eventLoop().submit(new Task(new Event("eone")));
// If it's blocking task, then we must use ctx.blockingTaskExecutor().
Or, propagate the ctx manually:
#Slf4j
public static class Task implements Runnable {
private final Event event;
private final ServiceRequestContext ctx;
Task(Event event) {
this.event = event;
ctx = ServiceRequestContext.current();
}
#Override
public void run() {
try (SafeCloseable ignored = ctx.push()) {
...
}
}
}
#minwoox, to simplify, my code would be looks like this
public class NameAuthRestApi {
JobExecutor executor = new JobExecutor();
#Post("/code")
public HttpResponse authCode(ServiceRequestContext ctx) {
try (SafeCloseable ignore = ctx.push()) {
executor.submit(new Task(new Event("eone")));
}
return HttpResponse.of("OK");
}
#Getter
#AllArgsConstructor
public static class Event {
private String name;
}
#RequiredArgsConstructor
#Slf4j
public static class Task implements Runnable {
final Event event;
#Override
public void run() {
// couldn't access ServiceRequestContext here
ServiceRequestContext ctx = ServiceRequestContext.currentOrNull();
log.info("ctx={}, event={}", ctx, event);
}
}
public static class JobExecutor {
ExecutorService executorService = Executors.newFixedThreadPool(2);
public void submit(Task task) {
executorService.submit(task);
}
}
}
I have a problem that whenever I create a process instance in Camunda Process Service, it does not write anything to ACT_HI_OP_LOG. I am not sure why it is not saving the histories into database.
#Component
#Order(Ordering.DEFAULT_ORDER + 1)
public class ProcessEngineConfiguration implements ProcessEnginePlugin {
private String tenantId;
#Override
public void preInit(ProcessEngineConfigurationImpl processEngineConfiguration) {
HistoryLevel historyLevel = new HistoryLevelFull();
processEngineConfiguration.setHistoryLevel(historyLevel);
processEngineConfiguration.setTenantCheckEnabled(true);
// processEngineConfiguration.setHistory(org.camunda.bpm.engine.ProcessEngineConfiguration.HISTORY_FULL);
processEngineConfiguration.setTenantIdProvider(new TenantIdProvider() {
#Override
public String provideTenantIdForProcessInstance(TenantIdProviderProcessInstanceContext ctx) {
return tenantId;
}
#Override
public String provideTenantIdForHistoricDecisionInstance(TenantIdProviderHistoricDecisionInstanceContext ctx) {
return tenantId;
}
#Override
public String provideTenantIdForCaseInstance(TenantIdProviderCaseInstanceContext ctx) {
return tenantId;
}
});
processEngineConfiguration.setJobExecutor(processEngineConfiguration.getJobExecutor());
}
This is how I start the process.
ProcessInstance pi = null;
try {
identityService.setAuthentication(getAuthentication());
pi = runtimeService.startProcessInstanceByKey(flowName, businessKey, variables);
} finally {
identityService.setAuthentication(null);
}
if (pi == null)
return null;
Did you check configuration?
historyLevelCheckEnabled default value is true.
Can you try to set that value false.
If you set that false, this check would not be performed.
I am curious about using RxJava to implement best effort retry in Akka, without Persistent Actors. The idea is to use Rx's retry method to keep asking until a response is received from the destination actor.
Other examples of this is hard to find. Are there any Akka gurus out there that could verify this implementation, or point me to a better solution?
Example:
public class RxWithAkka {
private final Logger LOGGER = LoggerFactory.getLogger(getClass());
public static final Timeout TIMEOUT = Timeout.apply(10, TimeUnit.MILLISECONDS);
private final ActorRef actor;
private final ActorSystem actorSystem;
public RxWithAkka(ActorSystem actorSystem) {
this.actorSystem = actorSystem;
this.actor = actorSystem.actorOf(Props.create(MyActor.class));
}
public Observable<Object> ping() {
return createObservable()
.doOnError(t -> LOGGER.warn(t.getMessage()))
.retry();
}
Observable<Object> createObservable() {
return Observable.create(subscriber -> {
LOGGER.info("Send ping");
Patterns.ask(actor, "ping", TIMEOUT)
.onComplete(new OnComplete<Object>() {
#Override
public void onComplete(Throwable failure, Object success) throws Throwable {
if (success != null) {
subscriber.onNext(success);
subscriber.onCompleted();
} else {
subscriber.onError(failure);
}
}
}, actorSystem.dispatcher());
});
}
}
Example actor to demonstrate message not received and timeout:
public class MyActor extends UntypedActor {
private int counter = 0;
#Override
public void onReceive(Object message) throws Exception {
switch (counter++) {
case 0:
// ignore message
break;
case 1:
// timeout
Thread.sleep(200);
break;
default:
getSender().tell("pong", getSelf());
}
}
}
}
Test:
public class RxWithAkkaTest {
#Test
public void testIt() throws Exception {
ActorSystem system = ActorSystem.create("system");
RxWithAkka example = new RxWithAkka(system);
String res = (String) example.ping().toBlocking().first();
assertThat(res).isEqualTo("pong");
}
}
In RxJava, you can use the timeout operator in conjunction with retry.
Instead using the EventStream instead ArchivedEventStream, when I run command alert(notification) message go to all connected socket except the original sender, how I also can send to original sender.
Here is my model and controller, using WebSocket
EventModel
public class EventModel {
// ~~~~~~~~~ Let's chat!
final EventStream<EventModel.Event> events = new EventStream<EventModel.Event>(100);
/**
* Get the event
*/
public EventStream<EventModel.Event> getEventStream() {
return events;
}
/**
* A user say something on the room
*/
public void _alert(Notification notification){
if(notification == null) return;
events.publish(new EventModel.NotificationEvent(notification));
}
// ~~~~~~~~~ Events
public static abstract class Event {
final public String type;
final public Long timestamp;
public boolean sended;
public Event(String type) {
this.sended = false;
this.type = type;
this.timestamp = System.currentTimeMillis();
}
}
public static class NotificationEvent extends EventModel.Event{
public final Notification notification;
public NotificationEvent(Notification notification) {
super("notification");
this.notification = notification;
}
public User getReceiver(){
return notification.receiver;
}
}
// EventModel factory
static EventModel instance = null;
public static EventModel get() {
if(instance == null) {
instance = new EventModel();
}
return instance;
}
//Alert notification
public static void alert(Notification notification){
get()._alert(notification);
}
}
And here is controller
public class MyWebSocket extends RootController {
public static class WebSocket extends WebSocketController {
public static void echo(Long userId) {
//Security
User user = User.findById(userId);
EventModel eventCentre = EventModel.get();
// Socket connected, join the chat room
EventStream<EventModel.Event> eventStrean = eventCentre.getEventStream();
// Loop while the socket is open
while(inbound.isOpen()) {
// Wait for an event (either something coming on the inbound socket channel, or ChatRoom messages)
Either<WebSocketEvent,EventModel.Event> e = await(Promise.waitEither(
inbound.nextEvent(),
eventStrean.nextEvent()
));
//Handle if get any notification
for(EventModel.NotificationEvent event: ClassOf(EventModel.NotificationEvent.class).match(e._2)) {
if(!event.getReceiver().equals(user)) continue;
outbound.send(event.notification.toJson());
}
// Case: The socket has been closed
for(WebSocketClose closed: SocketClosed.match(e._1)) {
disconnect();
}
}
}
}
}
To send to the original sender as well remove or comment out this line:
if(!event.getReceiver().equals(user)) continue;
one small issue to be aware of
https://github.com/playframework/play1/pull/709
Can i flush all the logs based on time interval using configuration file. Searched a lot. Didn't find any. Short cut is using Timer ourselves and flush all loggers. But wanted to know whether configuraiton file allows it.
The configuration file options are explained in the LogManager documentation. At this time, the only way to do this is via the configuration file is to use the 'config' option to install your custom code to flush all loggers and perform the timer management. If you need to access to the JVM lifecycle, you can create a custom handler that ignores all log records but listens to constructor and close method calls.
public class FlushAllHandler extends Handler {
private final ScheduledExecutorService ses;
private final Future<?> task;
public FlushAllHandler() {
//TODO: Look these up from the LogManager.
super.setLevel(Level.OFF); //Ignore all published records.
ses = Executors.newScheduledThreadPool(1);
long delay = 1L;
TimeUnit unit = TimeUnit.HOURS;
task = ses.scheduleWithFixedDelay(new Task(), delay, delay, unit);
}
#Override
public void publish(LogRecord record) {
//Allow a trigger filter to kick off a flush.
if (isLoggable(record)) {
ses.execute(new Task());
}
}
#Override
public void flush() {
}
#Override
public void close() throws SecurityException {
super.setLevel(Level.OFF);
task.cancel(false);
ses.shutdown();
try {
ses.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
ses.shutdownNow();
}
private static class Task implements Runnable {
Task() {
}
#Override
public void run() {
final ArrayList<Handler> handlers = new ArrayList<>();
final LogManager manager = LogManager.getLogManager();
synchronized (manager) { //avoid ConcurrentModificationException
final Enumeration<String> e = manager.getLoggerNames();
while (e.hasMoreElements()) {
final Logger l = manager.getLogger(e.nextElement());
if (l != null) {
Collections.addAll(handlers, l.getHandlers());
}
}
}
//Don't hold LogManager lock while flushing handlers.
for (Handler h : handlers) {
h.flush();
}
}
}
}