From 251dc0b0d0e5641864108c3814eae3e219e2615d Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 24 May 2023 13:08:45 +0200 Subject: [PATCH 01/21] SEBSERV-446 fixed --- .../servicelayer/dao/impl/ClientConnectionDAOImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java index 27406d9c..ba0abd37 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java @@ -814,10 +814,13 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO { .selectByExample() .where( ClientConnectionRecordDynamicSqlSupport.status, - SqlBuilder.isIn(ClientConnection.SECURE_CHECK_STATES)) + SqlBuilder.isEqualTo(ConnectionStatus.ACTIVE.name())) .and( ClientConnectionRecordDynamicSqlSupport.examId, SqlBuilder.isEqualTo(examId)) + .and( + ClientConnectionRecordDynamicSqlSupport.clientVersion, + SqlBuilder.isNotNull()) .and( ClientConnectionRecordDynamicSqlSupport.clientVersionGranted, SqlBuilder.isNull()) From d63f25fb45ffd3d169d7dcd7cee429e8ec63e694 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 25 May 2023 15:08:09 +0200 Subject: [PATCH 02/21] increased SEB connection cache to 100000 entries --- src/main/resources/config/ehcache.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/config/ehcache.xml b/src/main/resources/config/ehcache.xml index 74e0a0fa..81f2dddc 100644 --- a/src/main/resources/config/ehcache.xml +++ b/src/main/resources/config/ehcache.xml @@ -34,7 +34,7 @@ 24 - 3000 + 100000 From 314ce82c0074beb22df9abf99c4f465bed7f7968 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 25 May 2023 16:15:36 +0200 Subject: [PATCH 03/21] removed synchronization for check access on SEB ping --- .../session/impl/SEBClientSessionServiceImpl.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java index 7d668cb0..4b70baa7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java @@ -170,10 +170,8 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { private void processPing(final String connectionToken, final long timestamp, final int pingNumber) { - ClientConnectionDataInternal activeClientConnection = null; - synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { - activeClientConnection = this.examSessionCacheService.getClientConnection(connectionToken); - } + final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService + .getClientConnection(connectionToken); if (activeClientConnection != null) { activeClientConnection.notifyPing(timestamp, pingNumber); From e3b44cb60b1b19b522f1cb8bef18a3e6191eda97 Mon Sep 17 00:00:00 2001 From: anhefti Date: Fri, 26 May 2023 13:26:03 +0200 Subject: [PATCH 04/21] SEBSERV-445 new SEB event store strategy with background tasks --- .../servicelayer/session/ClientIndicator.java | 13 +- .../session/EventHandlingStrategy.java | 1 + .../session/SEBClientSessionService.java | 9 +- .../impl/AsyncBatchEventSaveStrategy.java | 5 +- .../impl/ClientConnectionDataInternal.java | 1 + .../impl/EventHandlingStrategyFactory.java | 1 + .../impl/SEBClientEventBatchStore.java | 278 ++++++++++++++++++ .../impl/SEBClientSessionServiceImpl.java | 68 ++--- .../AbstractLogLevelCountIndicator.java | 25 +- .../indicator/AbstractLogNumberIndicator.java | 23 +- .../PingIntervalClientIndicator.java | 9 +- .../weblayer/api/ExamAPI_V1_Controller.java | 5 +- .../config/application-dev-ws.properties | 2 +- .../gbl/model/ModelObjectJSONGenerator.java | 10 +- .../api/exam/SebConnectionTest.java | 8 +- .../services/ClientEventServiceTest.java | 67 +++-- 16 files changed, 384 insertions(+), 141 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java index 8c8b4cec..b61f1aad 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java @@ -14,10 +14,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.monitoring.IndicatorValue; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; /** A client indicator is a indicator value holder for a specific Indicator * on a running client connection. @@ -64,14 +62,9 @@ public interface ClientIndicator extends IndicatorValue { /** This gets called on a value change e.g.: when a ClientEvent was received. * NOTE: that this is called only on the same machine (server-instance) on that the ClientEvent was received. * - * @param event The ClientEvent instance */ - void notifyValueChange(ClientEvent event); - - /** This gets called on a value change e.g.: when a ClientEvent was received. - * NOTE: that this is called only on the same machine (server-instance) on that the ClientEvent was received. - * - * @param clientEventRecord The ClientEventRecord instance */ - void notifyValueChange(ClientEventRecord clientEventRecord); + * @param textValue The text based value + * @param numValue The value number */ + void notifyValueChange(String textValue, double numValue); /** This indicates if the indicator indicates an incident. This is the case if the actual indicator value * is above or below the max or min value defined by the indicator threshold settings. diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/EventHandlingStrategy.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/EventHandlingStrategy.java index a9c9b7df..2ef1ee28 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/EventHandlingStrategy.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/EventHandlingStrategy.java @@ -14,6 +14,7 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; /** A exam session SEB client event handling strategy implements a certain strategy to * store ClientEvent that are coming in within the specified endpoint in height frequency. */ +@Deprecated public interface EventHandlingStrategy extends Consumer { String EVENT_CONSUMER_STRATEGY_CONFIG_PROPERTY_KEY = "sebserver.webservice.api.exam.event-handling-strategy"; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java index b7f1ab14..1d342868 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java @@ -10,7 +10,6 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.util.Result; public interface SEBClientSessionService { @@ -39,7 +38,13 @@ public interface SEBClientSessionService { * * @param connectionToken the connection token * @param event The SEB client event data */ - void notifyClientEvent(String connectionToken, final ClientEvent event); + void notifyClientEvent(String connectionToken, String jsonBody); + +// /** Notify a SEB client event for live indication and storing to database. +// * +// * @param connectionToken the connection token +// * @param event The SEB client event data */ +// void notifyClientEvent(String connectionToken, final ClientEvent event); /** This is used to confirm SEB instructions that must be confirmed by the SEB client. * diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/AsyncBatchEventSaveStrategy.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/AsyncBatchEventSaveStrategy.java index 7639162e..27c35222 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/AsyncBatchEventSaveStrategy.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/AsyncBatchEventSaveStrategy.java @@ -77,7 +77,7 @@ public class AsyncBatchEventSaveStrategy implements EventHandlingStrategy { private final BlockingDeque eventQueue = new LinkedBlockingDeque<>(); private final BlockingDeque notificationQueue = new LinkedBlockingDeque<>(); private boolean workersRunning = false; - private boolean enabled = false; + private final boolean enabled = false; public AsyncBatchEventSaveStrategy( final SEBClientNotificationService sebClientNotificationService, @@ -95,7 +95,8 @@ public class AsyncBatchEventSaveStrategy implements EventHandlingStrategy { @Override public void enable() { - this.enabled = true; + log.info("AsyncBatchEventSaveStrategy is deprecated"); + //this.enabled = true; } @EventListener(SEBServerInitEvent.class) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java index 59a64d3c..590ebe9f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java @@ -39,6 +39,7 @@ public class ClientConnectionDataInternal extends ClientConnectionData { private static final Logger log = LoggerFactory.getLogger(ClientConnectionDataInternal.class); + // TODO why list for type? Is it possible to restrict to one per type? final EnumMap> indicatorMapping; PingIntervalClientIndicator pingIndicator = null; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/EventHandlingStrategyFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/EventHandlingStrategyFactory.java index 1b5e0fb8..b507d647 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/EventHandlingStrategyFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/EventHandlingStrategyFactory.java @@ -19,6 +19,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.EventHandlingStrate @Lazy @Service @WebServiceProfile +@Deprecated public class EventHandlingStrategyFactory { private final EventHandlingStrategy eventHandlingStrategy; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java new file mode 100644 index 00000000..78bb02f1 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2023 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.stream.Collectors; + +import javax.annotation.PreDestroy; + +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; + +import ch.ethz.seb.sebserver.gbl.api.JSONMapper; +import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; +import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification; +import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification.NotificationType; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Pair; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService; + +@Lazy +@Component +@WebServiceProfile +public class SEBClientEventBatchStore { + + private static final Logger log = LoggerFactory.getLogger(SEBClientEventBatchStore.class); + + private final SEBClientNotificationService sebClientNotificationService; + private final SqlSessionFactory sqlSessionFactory; + private final TransactionTemplate transactionTemplate; + private final ExamSessionCacheService examSessionCacheService; + private final JSONMapper jsonMapper; + + private final SqlSessionTemplate sqlSessionTemplate; + private final ClientEventRecordMapper clientEventMapper; + + public SEBClientEventBatchStore( + final SEBClientNotificationService sebClientNotificationService, + final SqlSessionFactory sqlSessionFactory, + final PlatformTransactionManager transactionManager, + final ExamSessionCacheService examSessionCacheService, + final JSONMapper jsonMapper) { + + this.sebClientNotificationService = sebClientNotificationService; + this.sqlSessionFactory = sqlSessionFactory; + this.transactionTemplate = new TransactionTemplate(transactionManager); + this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + this.examSessionCacheService = examSessionCacheService; + this.jsonMapper = jsonMapper; + + this.sqlSessionTemplate = new SqlSessionTemplate( + this.sqlSessionFactory, + ExecutorType.BATCH); + this.clientEventMapper = this.sqlSessionTemplate.getMapper( + ClientEventRecordMapper.class); + } + + private final BlockingDeque eventDataQueue = new LinkedBlockingDeque<>(); + private final Collection events = new ArrayList<>(); + + public void accept(final String connectionToken, final String jsonBody) { + this.eventDataQueue.add(new EventData( + connectionToken, + Utils.getMillisecondsNow(), + jsonBody)); + } + + public void accept(final EventData eventData) { + this.eventDataQueue.add(eventData); + } + + @Scheduled( + fixedDelayString = "${sebserver.webservice.api.exam.session.event.batch.task:1000}", + initialDelay = 1000) + public void processEvents() { + + final long startTime = Utils.getMillisecondsNow(); + + //if (log.isDebugEnabled()) { + final int size = this.eventDataQueue.size(); + if (size > 1000) { + log.warn("******* There are more then 1000 SEB client logs in the waiting queue: {}", size); + } + //} + + try { + + this.events.clear(); + this.eventDataQueue.drainTo(this.events); + + if (this.events.isEmpty()) { + return; + } + + System.out.println("********** processing: " + this.events.size()); + + final List events = this.events + .stream() + .map(this::convertData) + .map(this::storeNotifications) + .filter(Objects::nonNull) + .map(this::toEventRecord) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + this.transactionTemplate + .execute(status -> { + events.stream().forEach(this.clientEventMapper::insert); + return null; + }); + + this.sqlSessionTemplate.flushStatements(); + + //if (log.isTraceEnabled()) { + log.info("****** Processing SEB events tuck: {}", Utils.getMillisecondsNow() - startTime); + //} + + } catch (final Exception e) { + log.error("Failed to process SEB events from eventDataQueue: ", e); + } + } + + private EventData convertData(final EventData eventData) { + if (eventData == null || eventData.jsonBody == null) { + return eventData; + } + + try { + + final ClientEvent eventModel = this.jsonMapper.readValue( + eventData.jsonBody, + ClientEvent.class); + + eventData.setEvent(eventModel); + return eventData; + + } catch (final Exception e) { + log.error("Failed to convert SEB event JSON data to internal data for: {}", eventData); + return eventData; + } + } + + private EventData storeNotifications(final EventData eventData) { + try { + + if (!eventData.event.eventType.isNotificationEvent) { + return eventData; + } + + System.out.println("******* storeNotifications: " + eventData); + + final ClientConnectionDataInternal clientConnection = this.examSessionCacheService + .getClientConnection(eventData.connectionToken); + + final Pair typeAndPlainText = + ClientNotification.extractTypeAndPlainText(eventData.event.text); + final ClientNotification notification = new ClientNotification( + eventData.event.id, + clientConnection.getConnectionId(), + eventData.event.eventType, + eventData.event.getClientTime(), + eventData.event.getServerTime(), + (eventData.event.numValue != null) ? eventData.event.numValue.doubleValue() : null, + typeAndPlainText.b, + typeAndPlainText.a); + + switch (notification.eventType) { + case NOTIFICATION: { + this.sebClientNotificationService.newNotification(notification); + break; + } + case NOTIFICATION_CONFIRMED: { + this.sebClientNotificationService.confirmPendingNotification(notification); + break; + } + default: + } + + // skip this for further event processing + return null; + + } catch (final Exception e) { + log.error("Failed to verify and process notification for SEB event: {}", eventData); + return eventData; + } + } + + private ClientEventRecord toEventRecord(final EventData eventData) { + try { + final ClientConnectionDataInternal clientConnection = this.examSessionCacheService + .getClientConnection(eventData.connectionToken); + + if (clientConnection == null) { + log.warn("Failed to retrieve ClientConnection for token {}. Skip this event", + eventData.connectionToken); + return null; + } + + // handle indicator update + clientConnection + .getIndicatorMapping(eventData.event.eventType) + .forEach(indicator -> indicator.notifyValueChange( + eventData.event.text, + (eventData.event.numValue != null) ? eventData.event.numValue : Double.NaN)); + + return ClientEvent.toRecord(eventData.event, clientConnection.clientConnection.id); + + } catch (final Exception e) { + log.error( + "Unexpected error while converting SEB event data to record for: {} Skip this event", eventData, + e); + return null; + } + } + + @PreDestroy + protected void shutdown() { + log.info("Shutdown SEBClientEventBatchStore..."); + if (this.sqlSessionTemplate != null) { + try { + this.sqlSessionTemplate.destroy(); + } catch (final Exception e) { + log.error("Failed to close and destroy the SqlSessionTemplate for this thread: {}", + Thread.currentThread(), + e); + } + } + } + + public final static class EventData { + final String connectionToken; + final Long serverTime; + final String jsonBody; + ClientEvent event; + + public EventData(final String connectionToken, final Long serverTime, final String jsonBody) { + this.connectionToken = connectionToken; + this.serverTime = serverTime; + this.jsonBody = jsonBody; + this.event = null; + } + + public EventData(final String connectionToken, final Long serverTime, final ClientEvent event) { + this.connectionToken = connectionToken; + this.serverTime = serverTime; + this.jsonBody = null; + this.event = event; + } + + void setEvent(final ClientEvent event) { + this.event = event; + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java index 4b70baa7..7e7b182f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java @@ -8,7 +8,6 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; -import java.math.BigDecimal; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -26,14 +25,13 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Utils; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.EventHandlingStrategy; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientVersionService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchStore.EventData; @Lazy @Service @@ -45,7 +43,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { private final ClientConnectionDAO clientConnectionDAO; private final ExamSessionService examSessionService; private final ExamSessionCacheService examSessionCacheService; - private final EventHandlingStrategy eventHandlingStrategy; + private final SEBClientEventBatchStore sebClientEventBatchStore; private final SEBClientInstructionService sebInstructionService; private final ClientIndicatorFactory clientIndicatorFactory; private final InternalClientConnectionDataFactory internalClientConnectionDataFactory; @@ -55,7 +53,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { public SEBClientSessionServiceImpl( final ClientConnectionDAO clientConnectionDAO, final ExamSessionService examSessionService, - final EventHandlingStrategyFactory eventHandlingStrategyFactory, + final SEBClientEventBatchStore sebClientEventBatchStore, final SEBClientInstructionService sebInstructionService, final ClientIndicatorFactory clientIndicatorFactory, final InternalClientConnectionDataFactory internalClientConnectionDataFactory, @@ -65,7 +63,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { this.clientConnectionDAO = clientConnectionDAO; this.examSessionService = examSessionService; this.examSessionCacheService = examSessionService.getExamSessionCacheService(); - this.eventHandlingStrategy = eventHandlingStrategyFactory.get(); + this.sebClientEventBatchStore = sebClientEventBatchStore; this.sebInstructionService = sebInstructionService; this.clientIndicatorFactory = clientIndicatorFactory; this.internalClientConnectionDataFactory = internalClientConnectionDataFactory; @@ -127,32 +125,8 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { } @Override - public void notifyClientEvent( - final String connectionToken, - final ClientEvent event) { - - try { - final ClientConnectionDataInternal activeClientConnection = - this.examSessionService.getConnectionDataInternal(connectionToken); - - if (activeClientConnection != null) { - - // store event - this.eventHandlingStrategy.accept(ClientEvent.toRecord( - event, - activeClientConnection.getConnectionId())); - - // handle indicator update - activeClientConnection - .getIndicatorMapping(event.eventType) - .forEach(indicator -> indicator.notifyValueChange(event)); - - } else { - log.warn("No active ClientConnection found for connectionToken: {}", connectionToken); - } - } catch (final Exception e) { - log.error("Failed to process SEB client event: ", e); - } + public final void notifyClientEvent(final String connectionToken, final String jsonBody) { + this.sebClientEventBatchStore.accept(connectionToken, jsonBody); } @Override @@ -183,22 +157,28 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { final boolean missingPing = connection.getMissingPing(); final long millisecondsNow = Utils.getMillisecondsNow(); - final ClientEventRecord clientEventRecord = new ClientEventRecord( - null, - connection.getConnectionId(), - (missingPing) ? EventType.ERROR_LOG.id : EventType.INFO_LOG.id, - millisecondsNow, - millisecondsNow, - new BigDecimal(connection.pingIndicator.getValue()), - (missingPing) ? "Missing Client Ping" : "Client Ping Back To Normal"); + final String textValue = (missingPing) ? "Missing Client Ping" : "Client Ping Back To Normal"; + final double numValue = connection.pingIndicator.getValue(); - // store event and and flush cache - this.eventHandlingStrategy.accept(clientEventRecord); + final EventData eventData = new EventData( + connection.getClientConnection().connectionToken, + millisecondsNow, + new ClientEvent( + null, + connection.getConnectionId(), + (missingPing) ? EventType.ERROR_LOG : EventType.INFO_LOG, + millisecondsNow, + millisecondsNow, + numValue, + textValue)); + + // store missing-ping or ping-back event + this.sebClientEventBatchStore.accept(eventData); // update indicators - if (clientEventRecord.getType() != null && EventType.ERROR_LOG.id == clientEventRecord.getType()) { + if (EventType.ERROR_LOG == eventData.event.eventType) { connection.getIndicatorMapping(EventType.ERROR_LOG) - .forEach(indicator -> indicator.notifyValueChange(clientEventRecord)); + .forEach(indicator -> indicator.notifyValueChange(textValue, numValue)); } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java index 769cacf1..8e009a02 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java @@ -15,12 +15,10 @@ import org.mybatis.dynamic.sql.SqlCriterion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicator { @@ -38,13 +36,13 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato } @Override - public void notifyValueChange(final ClientEvent event) { - valueChanged(event.text); - } - - @Override - public void notifyValueChange(final ClientEventRecord clientEventRecord) { - valueChanged(clientEventRecord.getText()); + public final void notifyValueChange(final String textValue, final double numValue) { + if (this.tags == null || this.tags.length == 0 || hasTag(textValue)) { + if (super.ditributedIndicatorValueRecordId != null) { + this.distributedIndicatorValueService.incrementIndicatorValue(super.ditributedIndicatorValueRecordId); + } + this.currentValue = getValue() + 1d; + } } @Override @@ -112,13 +110,4 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato return result; } - private void valueChanged(final String eventText) { - if (this.tags == null || this.tags.length == 0 || hasTag(eventText)) { - if (super.ditributedIndicatorValueRecordId != null) { - this.distributedIndicatorValueService.incrementIndicatorValue(super.ditributedIndicatorValueRecordId); - } - this.currentValue = getValue() + 1d; - } - } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java index 2ad92b56..b117408e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java @@ -18,7 +18,6 @@ import org.mybatis.dynamic.sql.SqlCriterion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; @@ -41,31 +40,19 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator { } @Override - public void notifyValueChange(final ClientEvent event) { - valueChanged(event.text, event.getValue()); - } - - @Override - public void notifyValueChange(final ClientEventRecord clientEventRecord) { - final BigDecimal numericValue = clientEventRecord.getNumericValue(); - if (numericValue != null) { - valueChanged(clientEventRecord.getText(), numericValue.doubleValue()); - } - } - - private void valueChanged(final String text, final double value) { - if (this.tags == null || this.tags.length == 0 || hasTag(text)) { + public void notifyValueChange(final String textValue, final double numValue) { + if (this.tags == null || this.tags.length == 0 || hasTag(textValue)) { if (super.ditributedIndicatorValueRecordId != null) { if (!this.distributedIndicatorValueService.updateIndicatorValueAsync( this.ditributedIndicatorValueRecordId, - Double.valueOf(value).longValue())) { + Double.valueOf(numValue).longValue())) { this.currentValue = computeValueAt(Utils.getMillisecondsNow()); } else { - this.currentValue = value; + this.currentValue = numValue; } } else { - this.currentValue = value; + this.currentValue = numValue; } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java index dbd09771..47485823 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java @@ -19,8 +19,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; @Lazy @Component(IndicatorType.Names.LAST_PING) @@ -92,12 +90,7 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { } @Override - public void notifyValueChange(final ClientEvent event) { - - } - - @Override - public void notifyValueChange(final ClientEventRecord clientEventRecord) { + public void notifyValueChange(final String textValue, final double numValue) { } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java index 4d46c208..a3e0a8df 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java @@ -45,7 +45,6 @@ import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; -import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.RunningExamInfo; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Utils; @@ -358,10 +357,10 @@ public class ExamAPI_V1_Controller { @ResponseStatus(value = HttpStatus.NO_CONTENT) public void event( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, - @RequestBody(required = true) final ClientEvent event) { + @RequestBody(required = true) final String jsonBody) { this.sebClientSessionService - .notifyClientEvent(connectionToken, event); + .notifyClientEvent(connectionToken, jsonBody); } private Long getInstitutionId(final Principal principal) { diff --git a/src/main/resources/config/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties index 25b871d9..1840c26a 100644 --- a/src/main/resources/config/application-dev-ws.properties +++ b/src/main/resources/config/application-dev-ws.properties @@ -25,7 +25,7 @@ sebserver.webservice.clean-db-on-startup=false # webservice configuration sebserver.init.adminaccount.gen-on-init=false -sebserver.webservice.distributed=true +sebserver.webservice.distributed=false #sebserver.webservice.master.delay.threshold=10000 sebserver.webservice.http.external.scheme=http sebserver.webservice.http.external.servername=localhost diff --git a/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java b/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java index 5c112723..6d7b05fd 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java +++ b/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java @@ -72,7 +72,6 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType; import ch.ethz.seb.sebserver.gbl.model.user.UserMod; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.monitoring.SimpleIndicatorValue; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.PendingNotificationIndication; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ClientConnectionDataInternal; @@ -406,15 +405,8 @@ public class ModelObjectJSONGenerator { } @Override - public void notifyValueChange(final ClientEvent event) { + public void notifyValueChange(final String textValue, final double numValue) { // TODO Auto-generated method stub - - } - - @Override - public void notifyValueChange(final ClientEventRecord clientEventRecord) { - // TODO Auto-generated method stub - } @Override diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java index 39c6439d..fb2062d9 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java @@ -42,6 +42,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ClientConnectionDataInternal; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ExamSessionCacheService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchStore; @Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql", "classpath:data-test-additional.sql" }) public class SebConnectionTest extends ExamAPIIntegrationTester { @@ -56,6 +57,8 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { private ExamDAO examDAO; @Autowired private LmsAPIService lmsAPIService; + @Autowired + private SEBClientEventBatchStore sebClientEventBatchStore; @Before public void init() { @@ -558,6 +561,8 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { // check correct response assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus()); + this.sebClientEventBatchStore.processEvents(); + // check event stored on db List events = this.clientEventRecordMapper .selectByExample() @@ -582,6 +587,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { 10000.0, "testEvent2"); + this.sebClientEventBatchStore.processEvents(); // check correct response assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus()); @@ -616,7 +622,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { "testEvent1"); // check correct response assertTrue(HttpStatus.NO_CONTENT.value() == sendEvent.getStatus()); - + this.sebClientEventBatchStore.processEvents(); final List events = this.clientEventRecordMapper .selectByExample() .build() diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java index 4e70e74f..8625cc83 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java @@ -12,15 +12,12 @@ import static org.junit.Assert.*; import java.util.Collection; import java.util.Optional; -import java.util.concurrent.Executor; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.test.context.jdbc.Sql; -import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; +import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; @@ -35,6 +32,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientSessionService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchStore; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.AbstractLogIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.AbstractLogLevelCountIndicator; @@ -50,8 +48,12 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { @Autowired private SEBClientSessionService sebClientSessionService; @Autowired - @Qualifier(AsyncServiceSpringConfig.EXAM_API_EXECUTOR_BEAN_NAME) - private Executor executor; + private SEBClientEventBatchStore sebClientEventBatchStore; +// @Autowired +// @Qualifier(AsyncServiceSpringConfig.EXAM_API_EXECUTOR_BEAN_NAME) +// private Executor executor; + @Autowired + private JSONMapper jsonMapper; @Test public void testCreateLogEvents() { @@ -110,13 +112,15 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { this.sebClientSessionService.notifyClientEvent( "token1", - new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.ERROR_COUNT)); this.sebClientSessionService.notifyClientEvent( "token1", - new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.ERROR_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.ERROR_COUNT)); @@ -126,6 +130,15 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { } + private String writeValueAsString(final ClientEvent event) { + try { + return this.jsonMapper.writeValueAsString(event); + } catch (final Exception e) { + e.printStackTrace(); + return null; + } + } + @Test public void testInfoLogWithTagCountIndicator() { @@ -156,33 +169,39 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("0", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error"))); waitForExecutor(); assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("1", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error"))); waitForExecutor(); assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some error"))); waitForExecutor(); assertEquals("2", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.INFO_COUNT)); this.sebClientSessionService.notifyClientEvent( "token2", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some error"))); waitForExecutor(); // test reset indicator value and load it from persistent storage ((AbstractLogLevelCountIndicator) clientIndicator).reset(); @@ -191,13 +210,7 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { } private void waitForExecutor() { - try { - while (((ThreadPoolTaskExecutor) this.executor).getActiveCount() > 0) { - Thread.sleep(20); - } - } catch (final Exception e) { - e.printStackTrace(); - } + this.sebClientEventBatchStore.processEvents(); } @Test @@ -231,23 +244,27 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { this.sebClientSessionService.notifyClientEvent( "token3", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some info other")); + writeValueAsString( + new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, "some info other"))); waitForExecutor(); this.sebClientSessionService.notifyClientEvent( "token3", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, " some info other")); + writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 1.0, + " some info other"))); waitForExecutor(); assertEquals("--", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); this.sebClientSessionService.notifyClientEvent( "token3", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 90.0, " some info other")); + writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 90.0, + " some info other"))); waitForExecutor(); assertEquals("90", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); this.sebClientSessionService.notifyClientEvent( "token3", - new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 40.0, " some info other")); + writeValueAsString(new ClientEvent(null, connection.id, EventType.INFO_LOG, 1L, 1L, 40.0, + " some info other"))); waitForExecutor(); assertEquals("40", IndicatorValue.getDisplayValue(clientIndicator, IndicatorType.BATTERY_STATUS)); From a5335f1c985d37ecbb681d1820f27b98b43adf4f Mon Sep 17 00:00:00 2001 From: anhefti Date: Tue, 30 May 2023 09:21:53 +0200 Subject: [PATCH 05/21] SEBSERV-445 pings go to working queue just after arriving too now --- .../session/SEBClientSessionService.java | 6 - .../impl/ClientConnectionDataInternal.java | 4 +- .../impl/SEBClientEventBatchStore.java | 17 +-- .../session/impl/SEBClientPingService.java | 117 ++++++++++++++++++ .../impl/SEBClientSessionServiceImpl.java | 47 ++++--- .../impl/indicator/AbstractPingIndicator.java | 2 +- .../config/application-dev.properties | 1 + 7 files changed, 160 insertions(+), 34 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java index 1d342868..ae10241b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java @@ -40,12 +40,6 @@ public interface SEBClientSessionService { * @param event The SEB client event data */ void notifyClientEvent(String connectionToken, String jsonBody); -// /** Notify a SEB client event for live indication and storing to database. -// * -// * @param connectionToken the connection token -// * @param event The SEB client event data */ -// void notifyClientEvent(String connectionToken, final ClientEvent event); - /** This is used to confirm SEB instructions that must be confirmed by the SEB client. * * @param connectionToken The SEB client connection token diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java index 590ebe9f..a4699fef 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java @@ -86,9 +86,9 @@ public class ClientConnectionDataInternal extends ClientConnectionData { } } - public final void notifyPing(final long timestamp, final int pingNumber) { + public final void notifyPing(final long timestamp) { if (this.pingIndicator != null) { - this.pingIndicator.notifyPing(timestamp, pingNumber); + this.pingIndicator.notifyPing(timestamp); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java index 78bb02f1..0f04e487 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java @@ -93,18 +93,16 @@ public class SEBClientEventBatchStore { } @Scheduled( - fixedDelayString = "${sebserver.webservice.api.exam.session.event.batch.task:1000}", + fixedDelayString = "${sebserver.webservice.api.exam.session.event.batch.interval:1000}", initialDelay = 1000) public void processEvents() { final long startTime = Utils.getMillisecondsNow(); - //if (log.isDebugEnabled()) { final int size = this.eventDataQueue.size(); if (size > 1000) { log.warn("******* There are more then 1000 SEB client logs in the waiting queue: {}", size); } - //} try { @@ -115,8 +113,6 @@ public class SEBClientEventBatchStore { return; } - System.out.println("********** processing: " + this.events.size()); - final List events = this.events .stream() .map(this::convertData) @@ -134,9 +130,14 @@ public class SEBClientEventBatchStore { this.sqlSessionTemplate.flushStatements(); - //if (log.isTraceEnabled()) { - log.info("****** Processing SEB events tuck: {}", Utils.getMillisecondsNow() - startTime); - //} + if (log.isTraceEnabled()) { + log.trace("Processing {} SEB events tuck: {}", + this.events.size(), + Utils.getMillisecondsNow() - startTime); + } + // TODO just for debugging + System.out.println("***** Processing " + this.events.size() + " SEB events tuck: " + + (Utils.getMillisecondsNow() - startTime)); } catch (final Exception e) { log.error("Failed to process SEB events from eventDataQueue: ", e); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java new file mode 100644 index 00000000..e5b7602d --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.ehcache.impl.internal.concurrent.ConcurrentHashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService; + +@Lazy +@Component +@WebServiceProfile +public class SEBClientPingService { + + private static final Logger log = LoggerFactory.getLogger(SEBClientPingService.class); + + private final ExamSessionCacheService examSessionCacheService; + private final SEBClientInstructionService sebClientInstructionService; + + private final Map pings = new ConcurrentHashMap<>(); + private final Map instructions = new ConcurrentHashMap<>(); + + public SEBClientPingService( + final ExamSessionCacheService examSessionCacheService, + final SEBClientInstructionService sebClientInstructionService) { + + this.examSessionCacheService = examSessionCacheService; + this.sebClientInstructionService = sebClientInstructionService; + } + + @Scheduled( + fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:500}", + initialDelay = 1000) + public void processPings() { + if (this.pings.isEmpty()) { + return; + } + + final long startTime = Utils.getMillisecondsNow(); + + final int size = this.pings.size(); + if (size > 1000) { + log.warn("******* There are more then 1000 SEB client logs in the waiting queue: {}", size); + } + + try { + final Map pp = new HashMap<>(this.pings); + this.pings.clear(); + + pp.entrySet() + .stream() + .forEach(entry -> processPing(entry.getKey(), entry.getValue(), startTime)); + + if (log.isTraceEnabled()) { + log.trace("****** Processing {} SEB pings tuck: {}", Utils.getMillisecondsNow() - startTime); + } + // TODO just for debugging + System.out.println("***** Processing " + size + " SEB pings tuck: " + + (Utils.getMillisecondsNow() - startTime)); + + } catch (final Exception e) { + log.error("Failed to process SEB pings from pingDataQueue: ", e); + } + } + + public String notifyPing( + final String connectionToken, + final String instructionConfirm) { + + if (instructionConfirm != null) { + this.pings.put(connectionToken, instructionConfirm); + } else if (!this.pings.containsKey(connectionToken)) { + this.pings.put(connectionToken, StringUtils.EMPTY); + } + + return this.instructions.remove(connectionToken); + } + + private void processPing( + final String connectionToken, + final String instructionConfirm, + final long timestamp) { + + final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService + .getClientConnection(connectionToken); + + if (activeClientConnection != null) { + activeClientConnection.notifyPing(timestamp); + } + + if (instructionConfirm != StringUtils.EMPTY) { + this.sebClientInstructionService.confirmInstructionDone(connectionToken, instructionConfirm); + } + + final String instructionJSON = this.sebClientInstructionService.getInstructionJSON(connectionToken); + if (instructionJSON != null) { + this.instructions.put(connectionToken, instructionJSON); + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java index 7e7b182f..69fc9279 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java @@ -49,6 +49,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { private final InternalClientConnectionDataFactory internalClientConnectionDataFactory; private final SecurityKeyService securityKeyService; private final SEBClientVersionService sebClientVersionService; + private final SEBClientPingService sebClientPingService; public SEBClientSessionServiceImpl( final ClientConnectionDAO clientConnectionDAO, @@ -58,7 +59,8 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { final ClientIndicatorFactory clientIndicatorFactory, final InternalClientConnectionDataFactory internalClientConnectionDataFactory, final SecurityKeyService securityKeyService, - final SEBClientVersionService sebClientVersionService) { + final SEBClientVersionService sebClientVersionService, + final SEBClientPingService sebClientPingService) { this.clientConnectionDAO = clientConnectionDAO; this.examSessionService = examSessionService; @@ -69,6 +71,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { this.internalClientConnectionDataFactory = internalClientConnectionDataFactory; this.securityKeyService = securityKeyService; this.sebClientVersionService = sebClientVersionService; + this.sebClientPingService = sebClientPingService; } @Override @@ -115,15 +118,25 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { final int pingNumber, final String instructionConfirm) { - processPing(connectionToken, timestamp, pingNumber); - - if (instructionConfirm != null) { - this.sebInstructionService.confirmInstructionDone(connectionToken, instructionConfirm); - } - - return this.sebInstructionService.getInstructionJSON(connectionToken); + return this.sebClientPingService.notifyPing(connectionToken, instructionConfirm); } +// @Override +// public String notifyPing( +// final String connectionToken, +// final long timestamp, +// final int pingNumber, +// final String instructionConfirm) { +// +// processPing(connectionToken, timestamp, pingNumber); +// +// if (instructionConfirm != null) { +// this.sebInstructionService.confirmInstructionDone(connectionToken, instructionConfirm); +// } +// +// return this.sebInstructionService.getInstructionJSON(connectionToken); +// } + @Override public final void notifyClientEvent(final String connectionToken, final String jsonBody) { this.sebClientEventBatchStore.accept(connectionToken, jsonBody); @@ -142,15 +155,15 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { this.internalClientConnectionDataFactory.getGroupIds(clientConnection))); } - private void processPing(final String connectionToken, final long timestamp, final int pingNumber) { - - final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService - .getClientConnection(connectionToken); - - if (activeClientConnection != null) { - activeClientConnection.notifyPing(timestamp, pingNumber); - } - } +// private void processPing(final String connectionToken, final long timestamp, final int pingNumber) { +// +// final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService +// .getClientConnection(connectionToken); +// +// if (activeClientConnection != null) { +// activeClientConnection.notifyPing(timestamp); +// } +// } private void missingPingUpdate(final ClientConnectionDataInternal connection) { if (connection.pingIndicator.changeOnIncident()) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java index 14d0ed5b..b93e1aba 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java @@ -27,7 +27,7 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator { return this.EMPTY_SET; } - public final void notifyPing(final long timestamp, final int pingNumber) { + public final void notifyPing(final long timestamp) { super.currentValue = timestamp; if (!this.cachingEnabled && super.ditributedIndicatorValueRecordId != null) { diff --git a/src/main/resources/config/application-dev.properties b/src/main/resources/config/application-dev.properties index adbcaac7..5d9a3dbe 100644 --- a/src/main/resources/config/application-dev.properties +++ b/src/main/resources/config/application-dev.properties @@ -6,6 +6,7 @@ server.address=localhost server.port=8080 server.servlet.context-path=/ server.tomcat.uri-encoding=UTF-8 +server.http2.enabled=true logging.level.ROOT=INFO logging.level.ch=INFO From 527c575eeb91e418f32349a66bd31713ccff271c Mon Sep 17 00:00:00 2001 From: anhefti Date: Tue, 30 May 2023 10:42:43 +0200 Subject: [PATCH 06/21] removed ping number --- .../webservice/weblayer/api/ExamAPI_V1_Controller.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java index a3e0a8df..04a16689 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java @@ -328,14 +328,14 @@ public class ExamAPI_V1_Controller { public void ping(final HttpServletRequest request, final HttpServletResponse response) { final String connectionToken = request.getHeader(API.EXAM_API_SEB_CONNECTION_TOKEN); - final String pingNumString = request.getParameter(API.EXAM_API_PING_NUMBER); + //final String pingNumString = request.getParameter(API.EXAM_API_PING_NUMBER); final String instructionConfirm = request.getParameter(API.EXAM_API_PING_INSTRUCTION_CONFIRM); final String instruction = this.sebClientSessionService .notifyPing( connectionToken, - Utils.getMillisecondsNow(), - pingNumString != null ? Integer.parseInt(pingNumString) : -1, + 0, + 0, instructionConfirm); if (instruction == null) { From 48d3e5101cf42dde385ed8e880e2e3fb0ce03f31 Mon Sep 17 00:00:00 2001 From: anhefti Date: Tue, 30 May 2023 14:58:39 +0200 Subject: [PATCH 07/21] SEBSERV-445 adapt Tomcat connector settings --- .../java/ch/ethz/seb/sebserver/SEBServer.java | 26 +++++++++++++++++++ .../gbl/async/AsyncServiceSpringConfig.java | 1 + .../config/application-dev-ws.properties | 2 +- .../resources/config/application.properties | 4 +++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/SEBServer.java b/src/main/java/ch/ethz/seb/sebserver/SEBServer.java index da12f52d..6e630bd0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/SEBServer.java +++ b/src/main/java/ch/ethz/seb/sebserver/SEBServer.java @@ -10,10 +10,13 @@ package ch.ethz.seb.sebserver; import org.apache.catalina.connector.Connector; import org.apache.commons.lang3.BooleanUtils; +import org.apache.coyote.http11.AbstractHttp11Protocol; +import org.apache.coyote.http11.Http11NioProtocol; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -84,6 +87,29 @@ public class SEBServer { return firewall; } + @Bean + public WebServerFactoryCustomizer tomcatCustomizer() { + return (tomcat) -> tomcat.addConnectorCustomizers((connector) -> { + if (connector.getProtocolHandler() instanceof AbstractHttp11Protocol) { + System.out.println("*************** tomcatCustomizer"); + final AbstractHttp11Protocol protocolHandler = (AbstractHttp11Protocol) connector + .getProtocolHandler(); + protocolHandler.setKeepAliveTimeout(60000); + protocolHandler.setMaxKeepAliveRequests(3000); + protocolHandler.setUseKeepAliveResponseHeader(true); + protocolHandler.setMinSpareThreads(200); + protocolHandler.setProcessorCache(-1); + protocolHandler.setTcpNoDelay(true); + protocolHandler.setThreadPriority(Thread.NORM_PRIORITY + 1); + if (protocolHandler instanceof Http11NioProtocol) { + System.out.println("*************** Http11NioProtocol"); + ((Http11NioProtocol) protocolHandler).setPollerThreadPriority(Thread.MAX_PRIORITY); + } + + } + }); + } + private Connector redirectConnector(final Environment env) { final String sslPort = env.getRequiredProperty("server.port"); final String httpPort = env.getProperty("sebserver.ssl.redirect.html.port", "80"); diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java index 4f750ff1..50a20ce3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java @@ -34,6 +34,7 @@ public class AsyncServiceSpringConfig implements AsyncConfigurer { executor.setMaxPoolSize(42); executor.setQueueCapacity(11); executor.setThreadNamePrefix("asyncService-"); + executor.setThreadPriority(Thread.NORM_PRIORITY); executor.initialize(); executor.setWaitForTasksToCompleteOnShutdown(true); return executor; diff --git a/src/main/resources/config/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties index 1840c26a..25b871d9 100644 --- a/src/main/resources/config/application-dev-ws.properties +++ b/src/main/resources/config/application-dev-ws.properties @@ -25,7 +25,7 @@ sebserver.webservice.clean-db-on-startup=false # webservice configuration sebserver.init.adminaccount.gen-on-init=false -sebserver.webservice.distributed=false +sebserver.webservice.distributed=true #sebserver.webservice.master.delay.threshold=10000 sebserver.webservice.http.external.scheme=http sebserver.webservice.http.external.servername=localhost diff --git a/src/main/resources/config/application.properties b/src/main/resources/config/application.properties index 2f6bd64a..0079b89b 100644 --- a/src/main/resources/config/application.properties +++ b/src/main/resources/config/application.properties @@ -15,6 +15,10 @@ server.servlet.context-path=/ # Tomcat server.tomcat.max-threads=2000 server.tomcat.accept-count=300 +socket.soKeepAlive=true +socket.performanceConnectionTime=1 +socket.performanceLatency=2 +socket.performanceBandwidth=0 server.tomcat.uri-encoding=UTF-8 ### encoding From 9708d8610b33970af7dbc99509b233b01da04edf Mon Sep 17 00:00:00 2001 From: anhefti Date: Tue, 30 May 2023 15:40:17 +0200 Subject: [PATCH 08/21] SEBSERV-445 removed synchronized SEB connection cache access --- .../session/impl/ExamSessionServiceImpl.java | 6 +++--- .../weblayer/api/ExamMonitoringController.java | 12 ++++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java index d8d89ff9..7ca45549 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java @@ -378,9 +378,9 @@ public class ExamSessionServiceImpl implements ExamSessionService { @Override public ClientConnectionDataInternal getConnectionDataInternal(final String connectionToken) { - synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { - return this.examSessionCacheService.getClientConnection(connectionToken); - } + //synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { + return this.examSessionCacheService.getClientConnection(connectionToken); + //} } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java index 36aec030..b5b70eab 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java @@ -314,6 +314,8 @@ public class ExamMonitoringController { name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER, required = false) final String hiddenClientGroups) { + final long now = Utils.getMillisecondsNow(); + final Exam runningExam = checkPrivileges(institutionId, examId); final MonitoringSEBConnectionData monitoringSEBConnectionData = this.examSessionService @@ -322,21 +324,27 @@ public class ExamMonitoringController { createMonitoringFilter(hiddenStates, hiddenClientGroups)) .getOrThrow(); + MonitoringFullPageData monitoringFullPageData; if (this.examAdminService.isProctoringEnabled(runningExam).getOr(false)) { final Collection proctoringData = this.examProcotringRoomService .getProctoringCollectingRooms(examId) .getOrThrow(); - return new MonitoringFullPageData( + monitoringFullPageData = new MonitoringFullPageData( examId, monitoringSEBConnectionData, proctoringData); + } else { - return new MonitoringFullPageData( + monitoringFullPageData = new MonitoringFullPageData( examId, monitoringSEBConnectionData, Collections.emptyList()); } + + System.out.println("%%%%%%%% --> monitoring tuck: " + (Utils.getMillisecondsNow() - now)); + + return monitoringFullPageData; } @RequestMapping( From 6ccf74f9c12e0e3f7031b11c22c822fe5ca1469b Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 31 May 2023 09:12:46 +0200 Subject: [PATCH 09/21] SEBSERV-445 better ping batch strategy? --- .../session/impl/ExamSessionServiceImpl.java | 6 ++--- .../session/impl/SEBClientPingService.java | 23 +++++++++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java index 7ca45549..d8d89ff9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java @@ -378,9 +378,9 @@ public class ExamSessionServiceImpl implements ExamSessionService { @Override public ClientConnectionDataInternal getConnectionDataInternal(final String connectionToken) { - //synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { - return this.examSessionCacheService.getClientConnection(connectionToken); - //} + synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { + return this.examSessionCacheService.getClientConnection(connectionToken); + } } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java index e5b7602d..db0b4959 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java @@ -8,8 +8,9 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; -import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.ehcache.impl.internal.concurrent.ConcurrentHashMap; @@ -45,7 +46,7 @@ public class SEBClientPingService { } @Scheduled( - fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:500}", + fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:100}", initialDelay = 1000) public void processPings() { if (this.pings.isEmpty()) { @@ -60,12 +61,16 @@ public class SEBClientPingService { } try { - final Map pp = new HashMap<>(this.pings); - this.pings.clear(); + final Set connections = new HashSet<>(this.pings.keySet()); - pp.entrySet() - .stream() - .forEach(entry -> processPing(entry.getKey(), entry.getValue(), startTime)); + connections.stream().forEach(cid -> processPing( + cid, + this.pings.remove(cid), + Utils.getMillisecondsNow())); + +// pp.entrySet() +// .stream() +// .forEach(entry -> processPing(entry.getKey(), entry.getValue(), startTime)); if (log.isTraceEnabled()) { log.trace("****** Processing {} SEB pings tuck: {}", Utils.getMillisecondsNow() - startTime); @@ -83,6 +88,10 @@ public class SEBClientPingService { final String connectionToken, final String instructionConfirm) { + if (connectionToken == null) { + return null; + } + if (instructionConfirm != null) { this.pings.put(connectionToken, instructionConfirm); } else if (!this.pings.containsKey(connectionToken)) { From 9c82f2076366843aff2dbbe5528877534ffb713e Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 31 May 2023 11:34:25 +0200 Subject: [PATCH 10/21] SEBSERV-445 using DerreferedResult for client connections --- .../gbl/async/AsyncServiceSpringConfig.java | 17 +- .../session/impl/SEBClientPingService.java | 12 +- .../weblayer/api/ControllerConfig.java | 44 +-- .../weblayer/api/ExamAPI_V1_Controller.java | 328 ++++++++++++------ 4 files changed, 259 insertions(+), 142 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java index 50a20ce3..dcf25d9d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java @@ -17,7 +17,6 @@ import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; @Configuration @EnableAsync @@ -75,14 +74,14 @@ public class AsyncServiceSpringConfig implements AsyncConfigurer { return executor; } - @Bean - public ThreadPoolTaskScheduler threadPoolTaskScheduler() { - final ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); - threadPoolTaskScheduler.setPoolSize(5); - threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(false); - threadPoolTaskScheduler.setThreadNamePrefix("SEB-Server-BgTask-"); - return threadPoolTaskScheduler; - } +// @Bean +// public ThreadPoolTaskScheduler threadPoolTaskScheduler() { +// final ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); +// threadPoolTaskScheduler.setPoolSize(5); +// threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(false); +// threadPoolTaskScheduler.setThreadNamePrefix("SEB-Server-BgTask-"); +// return threadPoolTaskScheduler; +// } @Override public Executor getAsyncExecutor() { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java index db0b4959..8fb35fd0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java @@ -46,7 +46,7 @@ public class SEBClientPingService { } @Scheduled( - fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:100}", + fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:500}", initialDelay = 1000) public void processPings() { if (this.pings.isEmpty()) { @@ -84,14 +84,10 @@ public class SEBClientPingService { } } - public String notifyPing( + public final String notifyPing( final String connectionToken, final String instructionConfirm) { - if (connectionToken == null) { - return null; - } - if (instructionConfirm != null) { this.pings.put(connectionToken, instructionConfirm); } else if (!this.pings.containsKey(connectionToken)) { @@ -106,6 +102,10 @@ public class SEBClientPingService { final String instructionConfirm, final long timestamp) { + if (connectionToken == null) { + return; + } + final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService .getClientConnection(connectionToken); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java index 2557e075..436cb042 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java @@ -8,34 +8,28 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.task.AsyncTaskExecutor; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; - -@EnableAsync -@Configuration -@WebServiceProfile +//@EnableAsync +//@Configuration +//@WebServiceProfile +@Deprecated public class ControllerConfig implements WebMvcConfigurer { - @Override - public void configureAsyncSupport(final AsyncSupportConfigurer configurer) { - configurer.setTaskExecutor(threadPoolTaskExecutor()); - configurer.setDefaultTimeout(30000); - } - - public AsyncTaskExecutor threadPoolTaskExecutor() { - final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(7); - executor.setMaxPoolSize(42); - executor.setQueueCapacity(11); - executor.setThreadNamePrefix("mvc-"); - executor.initialize(); - return executor; - } +// @Override +// public void configureAsyncSupport(final AsyncSupportConfigurer configurer) { +// configurer.setTaskExecutor(threadPoolTaskExecutor()); +// configurer.setDefaultTimeout(30000); +// } +// +// public AsyncTaskExecutor threadPoolTaskExecutor() { +// final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); +// executor.setCorePoolSize(7); +// executor.setMaxPoolSize(42); +// executor.setQueueCapacity(11); +// executor.setThreadNamePrefix("mvc-"); +// executor.initialize(); +// return executor; +// } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java index 04a16689..210ecddf 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java @@ -36,6 +36,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.async.DeferredResult; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; @@ -93,7 +94,7 @@ public class ExamAPI_V1_Controller { method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public CompletableFuture> handshakeCreate( + public DeferredResult> handshakeCreate( @RequestParam(name = API.PARAM_INSTITUTION_ID, required = false) final Long instIdRequestParam, @RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = false) final Long examIdRequestParam, @RequestParam(name = API.EXAM_API_PARAM_CLIENT_ID, required = false) final String clientIdRequestParam, @@ -102,69 +103,133 @@ public class ExamAPI_V1_Controller { final HttpServletRequest request, final HttpServletResponse response) { - return CompletableFuture.supplyAsync( - () -> { + final DeferredResult> deferredResult = new DeferredResult<>(); + this.executor.execute(() -> { + final POSTMapper mapper = new POSTMapper(formParams, request.getQueryString()); - final POSTMapper mapper = new POSTMapper(formParams, request.getQueryString()); + final String remoteAddr = this.getClientAddress(request); + final Long institutionId = (instIdRequestParam != null) + ? instIdRequestParam + : mapper.getLong(API.PARAM_INSTITUTION_ID); + final Long examId = (examIdRequestParam != null) + ? examIdRequestParam + : mapper.getLong(API.EXAM_API_PARAM_EXAM_ID); + final String clientId = (clientIdRequestParam != null) + ? clientIdRequestParam + : mapper.getString(API.EXAM_API_PARAM_CLIENT_ID); - final String remoteAddr = this.getClientAddress(request); - final Long institutionId = (instIdRequestParam != null) - ? instIdRequestParam - : mapper.getLong(API.PARAM_INSTITUTION_ID); - final Long examId = (examIdRequestParam != null) - ? examIdRequestParam - : mapper.getLong(API.EXAM_API_PARAM_EXAM_ID); - final String clientId = (clientIdRequestParam != null) - ? clientIdRequestParam - : mapper.getString(API.EXAM_API_PARAM_CLIENT_ID); + // Create and get new ClientConnection if all integrity checks passes + final ClientConnection clientConnection = this.sebClientConnectionService + .createClientConnection( + principal, + institutionId, + remoteAddr, + mapper.getString(API.EXAM_API_PARAM_SEB_VERSION), + mapper.getString(API.EXAM_API_PARAM_SEB_OS_NAME), + mapper.getString(API.EXAM_API_PARAM_SEB_MACHINE_NAME), + examId, + clientId) + .getOrThrow(); - // Create and get new ClientConnection if all integrity checks passes - final ClientConnection clientConnection = this.sebClientConnectionService - .createClientConnection( - principal, - institutionId, - remoteAddr, - mapper.getString(API.EXAM_API_PARAM_SEB_VERSION), - mapper.getString(API.EXAM_API_PARAM_SEB_OS_NAME), - mapper.getString(API.EXAM_API_PARAM_SEB_MACHINE_NAME), - examId, - clientId) - .getOrThrow(); + response.setHeader( + API.EXAM_API_SEB_CONNECTION_TOKEN, + clientConnection.connectionToken); - response.setHeader( - API.EXAM_API_SEB_CONNECTION_TOKEN, - clientConnection.connectionToken); + // Crate list of running exams + List result; + if (examId == null) { + result = this.examSessionService.getRunningExamsForInstitution(institutionId) + .getOrThrow() + .stream() + .map(this::createRunningExamInfo) + .filter(this::checkConsistency) + .collect(Collectors.toList()); + } else { - // Crate list of running exams - List result; - if (examId == null) { - result = this.examSessionService.getRunningExamsForInstitution(institutionId) - .getOrThrow() - .stream() - .map(this::createRunningExamInfo) - .filter(this::checkConsistency) - .collect(Collectors.toList()); - } else { + final Exam exam = this.examSessionService + .getExamDAO() + .byPK(examId) + .getOrThrow(); - final Exam exam = this.examSessionService - .getExamDAO() - .byPK(examId) - .getOrThrow(); + result = Arrays.asList(createRunningExamInfo(exam)); + processASKSalt(response, clientConnection); + processAlternativeBEK(response, clientConnection.examId); + } - result = Arrays.asList(createRunningExamInfo(exam)); - processASKSalt(response, clientConnection); - processAlternativeBEK(response, clientConnection.examId); - } + if (result.isEmpty()) { + log.warn( + "There are no currently running exams for institution: {}. SEB connection creation denied", + institutionId); + } - if (result.isEmpty()) { - log.warn( - "There are no currently running exams for institution: {}. SEB connection creation denied", - institutionId); - } + deferredResult.setResult(result); + }); - return result; - }, - this.executor); + return deferredResult; + +// return CompletableFuture.supplyAsync( +// () -> { +// +// final POSTMapper mapper = new POSTMapper(formParams, request.getQueryString()); +// +// final String remoteAddr = this.getClientAddress(request); +// final Long institutionId = (instIdRequestParam != null) +// ? instIdRequestParam +// : mapper.getLong(API.PARAM_INSTITUTION_ID); +// final Long examId = (examIdRequestParam != null) +// ? examIdRequestParam +// : mapper.getLong(API.EXAM_API_PARAM_EXAM_ID); +// final String clientId = (clientIdRequestParam != null) +// ? clientIdRequestParam +// : mapper.getString(API.EXAM_API_PARAM_CLIENT_ID); +// +// // Create and get new ClientConnection if all integrity checks passes +// final ClientConnection clientConnection = this.sebClientConnectionService +// .createClientConnection( +// principal, +// institutionId, +// remoteAddr, +// mapper.getString(API.EXAM_API_PARAM_SEB_VERSION), +// mapper.getString(API.EXAM_API_PARAM_SEB_OS_NAME), +// mapper.getString(API.EXAM_API_PARAM_SEB_MACHINE_NAME), +// examId, +// clientId) +// .getOrThrow(); +// +// response.setHeader( +// API.EXAM_API_SEB_CONNECTION_TOKEN, +// clientConnection.connectionToken); +// +// // Crate list of running exams +// List result; +// if (examId == null) { +// result = this.examSessionService.getRunningExamsForInstitution(institutionId) +// .getOrThrow() +// .stream() +// .map(this::createRunningExamInfo) +// .filter(this::checkConsistency) +// .collect(Collectors.toList()); +// } else { +// +// final Exam exam = this.examSessionService +// .getExamDAO() +// .byPK(examId) +// .getOrThrow(); +// +// result = Arrays.asList(createRunningExamInfo(exam)); +// processASKSalt(response, clientConnection); +// processAlternativeBEK(response, clientConnection.examId); +// } +// +// if (result.isEmpty()) { +// log.warn( +// "There are no currently running exams for institution: {}. SEB connection creation denied", +// institutionId); +// } +// +// return result; +// }, +// this.executor); } private boolean checkConsistency(final RunningExamInfo info) { @@ -183,7 +248,7 @@ public class ExamAPI_V1_Controller { path = API.EXAM_API_HANDSHAKE_ENDPOINT, method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - public CompletableFuture handshakeUpdate( + public DeferredResult handshakeUpdate( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, @RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = false) final Long examId, @RequestParam(name = API.EXAM_API_USER_SESSION_ID, required = false) final String userSessionId, @@ -198,39 +263,71 @@ public class ExamAPI_V1_Controller { final HttpServletRequest request, final HttpServletResponse response) { - return CompletableFuture.runAsync( - () -> { + final DeferredResult deferredResult = new DeferredResult<>(); + this.executor.execute(() -> { + { - final String remoteAddr = this.getClientAddress(request); - final Long institutionId = getInstitutionId(principal); + final String remoteAddr = this.getClientAddress(request); + final Long institutionId = getInstitutionId(principal); - final ClientConnection clientConnection = this.sebClientConnectionService - .updateClientConnection( - connectionToken, - institutionId, - examId, - remoteAddr, - sebVersion, - sebOSName, - sebMachinName, - userSessionId, - clientId, - browserSignatureKey) - .getOrThrow(); + final ClientConnection clientConnection = this.sebClientConnectionService + .updateClientConnection( + connectionToken, + institutionId, + examId, + remoteAddr, + sebVersion, + sebOSName, + sebMachinName, + userSessionId, + clientId, + browserSignatureKey) + .getOrThrow(); - if (clientConnection.examId != null) { - processASKSalt(response, clientConnection); - processAlternativeBEK(response, clientConnection.examId); - } - }, - this.executor); + if (clientConnection.examId != null) { + processASKSalt(response, clientConnection); + processAlternativeBEK(response, clientConnection.examId); + } + + deferredResult.setResult(null); + } + }); + + return deferredResult; + +// return CompletableFuture.runAsync( +// () -> { +// +// final String remoteAddr = this.getClientAddress(request); +// final Long institutionId = getInstitutionId(principal); +// +// final ClientConnection clientConnection = this.sebClientConnectionService +// .updateClientConnection( +// connectionToken, +// institutionId, +// examId, +// remoteAddr, +// sebVersion, +// sebOSName, +// sebMachinName, +// userSessionId, +// clientId, +// browserSignatureKey) +// .getOrThrow(); +// +// if (clientConnection.examId != null) { +// processASKSalt(response, clientConnection); +// processAlternativeBEK(response, clientConnection.examId); +// } +// }, +// this.executor); } @RequestMapping( path = API.EXAM_API_HANDSHAKE_ENDPOINT, method = RequestMethod.PUT, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - public CompletableFuture handshakeEstablish( + public DeferredResult handshakeEstablish( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, @RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = false) final Long examId, @RequestParam(name = API.EXAM_API_USER_SESSION_ID, required = false) final String userSessionId, @@ -245,31 +342,58 @@ public class ExamAPI_V1_Controller { final HttpServletRequest request, final HttpServletResponse response) { - return CompletableFuture.runAsync( - () -> { + final DeferredResult deferredResult = new DeferredResult<>(); + this.executor.execute(() -> { - final String remoteAddr = this.getClientAddress(request); - final Long institutionId = getInstitutionId(principal); + final String remoteAddr = this.getClientAddress(request); + final Long institutionId = getInstitutionId(principal); - final ClientConnection clientConnection = this.sebClientConnectionService - .establishClientConnection( - connectionToken, - institutionId, - examId, - remoteAddr, - sebVersion, - sebOSName, - sebMachinName, - userSessionId, - clientId, - browserSignatureKey) - .getOrThrow(); + final ClientConnection clientConnection = this.sebClientConnectionService + .establishClientConnection( + connectionToken, + institutionId, + examId, + remoteAddr, + sebVersion, + sebOSName, + sebMachinName, + userSessionId, + clientId, + browserSignatureKey) + .getOrThrow(); - if (clientConnection.examId != null) { - processAlternativeBEK(response, clientConnection.examId); - } - }, - this.executor); + if (clientConnection.examId != null) { + processAlternativeBEK(response, clientConnection.examId); + } + + deferredResult.setResult(null); + }); + return deferredResult; +// return CompletableFuture.runAsync( +// () -> { +// +// final String remoteAddr = this.getClientAddress(request); +// final Long institutionId = getInstitutionId(principal); +// +// final ClientConnection clientConnection = this.sebClientConnectionService +// .establishClientConnection( +// connectionToken, +// institutionId, +// examId, +// remoteAddr, +// sebVersion, +// sebOSName, +// sebMachinName, +// userSessionId, +// clientId, +// browserSignatureKey) +// .getOrThrow(); +// +// if (clientConnection.examId != null) { +// processAlternativeBEK(response, clientConnection.examId); +// } +// }, +// this.executor); } @RequestMapping( From 62a6db12c6b9e1e55a89ea251514f1c2ea404201 Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 31 May 2023 12:57:06 +0200 Subject: [PATCH 11/21] revert DeferredResult --- .../java/ch/ethz/seb/sebserver/SEBServer.java | 1 + .../weblayer/api/ExamAPI_V1_Controller.java | 328 ++++++------------ 2 files changed, 103 insertions(+), 226 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/SEBServer.java b/src/main/java/ch/ethz/seb/sebserver/SEBServer.java index 6e630bd0..d3018c4d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/SEBServer.java +++ b/src/main/java/ch/ethz/seb/sebserver/SEBServer.java @@ -101,6 +101,7 @@ public class SEBServer { protocolHandler.setProcessorCache(-1); protocolHandler.setTcpNoDelay(true); protocolHandler.setThreadPriority(Thread.NORM_PRIORITY + 1); + protocolHandler.setMaxConnections(2000); if (protocolHandler instanceof Http11NioProtocol) { System.out.println("*************** Http11NioProtocol"); ((Http11NioProtocol) protocolHandler).setPollerThreadPriority(Thread.MAX_PRIORITY); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java index 210ecddf..04a16689 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java @@ -36,7 +36,6 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.request.async.DeferredResult; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; @@ -94,7 +93,7 @@ public class ExamAPI_V1_Controller { method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public DeferredResult> handshakeCreate( + public CompletableFuture> handshakeCreate( @RequestParam(name = API.PARAM_INSTITUTION_ID, required = false) final Long instIdRequestParam, @RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = false) final Long examIdRequestParam, @RequestParam(name = API.EXAM_API_PARAM_CLIENT_ID, required = false) final String clientIdRequestParam, @@ -103,133 +102,69 @@ public class ExamAPI_V1_Controller { final HttpServletRequest request, final HttpServletResponse response) { - final DeferredResult> deferredResult = new DeferredResult<>(); - this.executor.execute(() -> { - final POSTMapper mapper = new POSTMapper(formParams, request.getQueryString()); + return CompletableFuture.supplyAsync( + () -> { - final String remoteAddr = this.getClientAddress(request); - final Long institutionId = (instIdRequestParam != null) - ? instIdRequestParam - : mapper.getLong(API.PARAM_INSTITUTION_ID); - final Long examId = (examIdRequestParam != null) - ? examIdRequestParam - : mapper.getLong(API.EXAM_API_PARAM_EXAM_ID); - final String clientId = (clientIdRequestParam != null) - ? clientIdRequestParam - : mapper.getString(API.EXAM_API_PARAM_CLIENT_ID); + final POSTMapper mapper = new POSTMapper(formParams, request.getQueryString()); - // Create and get new ClientConnection if all integrity checks passes - final ClientConnection clientConnection = this.sebClientConnectionService - .createClientConnection( - principal, - institutionId, - remoteAddr, - mapper.getString(API.EXAM_API_PARAM_SEB_VERSION), - mapper.getString(API.EXAM_API_PARAM_SEB_OS_NAME), - mapper.getString(API.EXAM_API_PARAM_SEB_MACHINE_NAME), - examId, - clientId) - .getOrThrow(); + final String remoteAddr = this.getClientAddress(request); + final Long institutionId = (instIdRequestParam != null) + ? instIdRequestParam + : mapper.getLong(API.PARAM_INSTITUTION_ID); + final Long examId = (examIdRequestParam != null) + ? examIdRequestParam + : mapper.getLong(API.EXAM_API_PARAM_EXAM_ID); + final String clientId = (clientIdRequestParam != null) + ? clientIdRequestParam + : mapper.getString(API.EXAM_API_PARAM_CLIENT_ID); - response.setHeader( - API.EXAM_API_SEB_CONNECTION_TOKEN, - clientConnection.connectionToken); + // Create and get new ClientConnection if all integrity checks passes + final ClientConnection clientConnection = this.sebClientConnectionService + .createClientConnection( + principal, + institutionId, + remoteAddr, + mapper.getString(API.EXAM_API_PARAM_SEB_VERSION), + mapper.getString(API.EXAM_API_PARAM_SEB_OS_NAME), + mapper.getString(API.EXAM_API_PARAM_SEB_MACHINE_NAME), + examId, + clientId) + .getOrThrow(); - // Crate list of running exams - List result; - if (examId == null) { - result = this.examSessionService.getRunningExamsForInstitution(institutionId) - .getOrThrow() - .stream() - .map(this::createRunningExamInfo) - .filter(this::checkConsistency) - .collect(Collectors.toList()); - } else { + response.setHeader( + API.EXAM_API_SEB_CONNECTION_TOKEN, + clientConnection.connectionToken); - final Exam exam = this.examSessionService - .getExamDAO() - .byPK(examId) - .getOrThrow(); + // Crate list of running exams + List result; + if (examId == null) { + result = this.examSessionService.getRunningExamsForInstitution(institutionId) + .getOrThrow() + .stream() + .map(this::createRunningExamInfo) + .filter(this::checkConsistency) + .collect(Collectors.toList()); + } else { - result = Arrays.asList(createRunningExamInfo(exam)); - processASKSalt(response, clientConnection); - processAlternativeBEK(response, clientConnection.examId); - } + final Exam exam = this.examSessionService + .getExamDAO() + .byPK(examId) + .getOrThrow(); - if (result.isEmpty()) { - log.warn( - "There are no currently running exams for institution: {}. SEB connection creation denied", - institutionId); - } + result = Arrays.asList(createRunningExamInfo(exam)); + processASKSalt(response, clientConnection); + processAlternativeBEK(response, clientConnection.examId); + } - deferredResult.setResult(result); - }); + if (result.isEmpty()) { + log.warn( + "There are no currently running exams for institution: {}. SEB connection creation denied", + institutionId); + } - return deferredResult; - -// return CompletableFuture.supplyAsync( -// () -> { -// -// final POSTMapper mapper = new POSTMapper(formParams, request.getQueryString()); -// -// final String remoteAddr = this.getClientAddress(request); -// final Long institutionId = (instIdRequestParam != null) -// ? instIdRequestParam -// : mapper.getLong(API.PARAM_INSTITUTION_ID); -// final Long examId = (examIdRequestParam != null) -// ? examIdRequestParam -// : mapper.getLong(API.EXAM_API_PARAM_EXAM_ID); -// final String clientId = (clientIdRequestParam != null) -// ? clientIdRequestParam -// : mapper.getString(API.EXAM_API_PARAM_CLIENT_ID); -// -// // Create and get new ClientConnection if all integrity checks passes -// final ClientConnection clientConnection = this.sebClientConnectionService -// .createClientConnection( -// principal, -// institutionId, -// remoteAddr, -// mapper.getString(API.EXAM_API_PARAM_SEB_VERSION), -// mapper.getString(API.EXAM_API_PARAM_SEB_OS_NAME), -// mapper.getString(API.EXAM_API_PARAM_SEB_MACHINE_NAME), -// examId, -// clientId) -// .getOrThrow(); -// -// response.setHeader( -// API.EXAM_API_SEB_CONNECTION_TOKEN, -// clientConnection.connectionToken); -// -// // Crate list of running exams -// List result; -// if (examId == null) { -// result = this.examSessionService.getRunningExamsForInstitution(institutionId) -// .getOrThrow() -// .stream() -// .map(this::createRunningExamInfo) -// .filter(this::checkConsistency) -// .collect(Collectors.toList()); -// } else { -// -// final Exam exam = this.examSessionService -// .getExamDAO() -// .byPK(examId) -// .getOrThrow(); -// -// result = Arrays.asList(createRunningExamInfo(exam)); -// processASKSalt(response, clientConnection); -// processAlternativeBEK(response, clientConnection.examId); -// } -// -// if (result.isEmpty()) { -// log.warn( -// "There are no currently running exams for institution: {}. SEB connection creation denied", -// institutionId); -// } -// -// return result; -// }, -// this.executor); + return result; + }, + this.executor); } private boolean checkConsistency(final RunningExamInfo info) { @@ -248,7 +183,7 @@ public class ExamAPI_V1_Controller { path = API.EXAM_API_HANDSHAKE_ENDPOINT, method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - public DeferredResult handshakeUpdate( + public CompletableFuture handshakeUpdate( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, @RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = false) final Long examId, @RequestParam(name = API.EXAM_API_USER_SESSION_ID, required = false) final String userSessionId, @@ -263,71 +198,39 @@ public class ExamAPI_V1_Controller { final HttpServletRequest request, final HttpServletResponse response) { - final DeferredResult deferredResult = new DeferredResult<>(); - this.executor.execute(() -> { - { + return CompletableFuture.runAsync( + () -> { - final String remoteAddr = this.getClientAddress(request); - final Long institutionId = getInstitutionId(principal); + final String remoteAddr = this.getClientAddress(request); + final Long institutionId = getInstitutionId(principal); - final ClientConnection clientConnection = this.sebClientConnectionService - .updateClientConnection( - connectionToken, - institutionId, - examId, - remoteAddr, - sebVersion, - sebOSName, - sebMachinName, - userSessionId, - clientId, - browserSignatureKey) - .getOrThrow(); + final ClientConnection clientConnection = this.sebClientConnectionService + .updateClientConnection( + connectionToken, + institutionId, + examId, + remoteAddr, + sebVersion, + sebOSName, + sebMachinName, + userSessionId, + clientId, + browserSignatureKey) + .getOrThrow(); - if (clientConnection.examId != null) { - processASKSalt(response, clientConnection); - processAlternativeBEK(response, clientConnection.examId); - } - - deferredResult.setResult(null); - } - }); - - return deferredResult; - -// return CompletableFuture.runAsync( -// () -> { -// -// final String remoteAddr = this.getClientAddress(request); -// final Long institutionId = getInstitutionId(principal); -// -// final ClientConnection clientConnection = this.sebClientConnectionService -// .updateClientConnection( -// connectionToken, -// institutionId, -// examId, -// remoteAddr, -// sebVersion, -// sebOSName, -// sebMachinName, -// userSessionId, -// clientId, -// browserSignatureKey) -// .getOrThrow(); -// -// if (clientConnection.examId != null) { -// processASKSalt(response, clientConnection); -// processAlternativeBEK(response, clientConnection.examId); -// } -// }, -// this.executor); + if (clientConnection.examId != null) { + processASKSalt(response, clientConnection); + processAlternativeBEK(response, clientConnection.examId); + } + }, + this.executor); } @RequestMapping( path = API.EXAM_API_HANDSHAKE_ENDPOINT, method = RequestMethod.PUT, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - public DeferredResult handshakeEstablish( + public CompletableFuture handshakeEstablish( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, @RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = false) final Long examId, @RequestParam(name = API.EXAM_API_USER_SESSION_ID, required = false) final String userSessionId, @@ -342,58 +245,31 @@ public class ExamAPI_V1_Controller { final HttpServletRequest request, final HttpServletResponse response) { - final DeferredResult deferredResult = new DeferredResult<>(); - this.executor.execute(() -> { + return CompletableFuture.runAsync( + () -> { - final String remoteAddr = this.getClientAddress(request); - final Long institutionId = getInstitutionId(principal); + final String remoteAddr = this.getClientAddress(request); + final Long institutionId = getInstitutionId(principal); - final ClientConnection clientConnection = this.sebClientConnectionService - .establishClientConnection( - connectionToken, - institutionId, - examId, - remoteAddr, - sebVersion, - sebOSName, - sebMachinName, - userSessionId, - clientId, - browserSignatureKey) - .getOrThrow(); + final ClientConnection clientConnection = this.sebClientConnectionService + .establishClientConnection( + connectionToken, + institutionId, + examId, + remoteAddr, + sebVersion, + sebOSName, + sebMachinName, + userSessionId, + clientId, + browserSignatureKey) + .getOrThrow(); - if (clientConnection.examId != null) { - processAlternativeBEK(response, clientConnection.examId); - } - - deferredResult.setResult(null); - }); - return deferredResult; -// return CompletableFuture.runAsync( -// () -> { -// -// final String remoteAddr = this.getClientAddress(request); -// final Long institutionId = getInstitutionId(principal); -// -// final ClientConnection clientConnection = this.sebClientConnectionService -// .establishClientConnection( -// connectionToken, -// institutionId, -// examId, -// remoteAddr, -// sebVersion, -// sebOSName, -// sebMachinName, -// userSessionId, -// clientId, -// browserSignatureKey) -// .getOrThrow(); -// -// if (clientConnection.examId != null) { -// processAlternativeBEK(response, clientConnection.examId); -// } -// }, -// this.executor); + if (clientConnection.examId != null) { + processAlternativeBEK(response, clientConnection.examId); + } + }, + this.executor); } @RequestMapping( From e0708a2be1d9be5e4c5f8fa5e4147bd9816303be Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 31 May 2023 12:57:33 +0200 Subject: [PATCH 12/21] try monitoring update everytime --- .../gui/service/session/ClientConnectionTable.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java index 9e766723..11eb03c7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java @@ -371,7 +371,6 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate } public void updateGUI() { - if (this.needsSort) { sortTable(); } @@ -511,12 +510,12 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate private void update(final TableItem tableItem, final boolean force) { updateDuplicateColor(tableItem); - if (force || this.dataChanged) { - updateData(tableItem); - } - if (force || this.indicatorValueChanged) { - updateIndicatorValues(tableItem); - } + //if (force || this.dataChanged) { + updateData(tableItem); + //} + //if (force || this.indicatorValueChanged) { + updateIndicatorValues(tableItem); + //} this.dataChanged = false; this.indicatorValueChanged = false; } From b7c8f3c1d43f8961d3418d024d08c5d675de72a8 Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 31 May 2023 13:08:05 +0200 Subject: [PATCH 13/21] fixed monitoring update delay for indicators? --- .../service/session/ClientConnectionTable.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java index 11eb03c7..c33793d3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java @@ -510,12 +510,12 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate private void update(final TableItem tableItem, final boolean force) { updateDuplicateColor(tableItem); - //if (force || this.dataChanged) { - updateData(tableItem); - //} - //if (force || this.indicatorValueChanged) { - updateIndicatorValues(tableItem); - //} + if (force || this.dataChanged) { + updateData(tableItem); + } + if (force || this.indicatorValueChanged) { + updateIndicatorValues(tableItem); + } this.dataChanged = false; this.indicatorValueChanged = false; } @@ -724,10 +724,12 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate this.indicatorWeights[i] = -1; } } + + this.monitoringData = monitoringData; + if (this.indicatorValueChanged) { updateIndicatorWeight(); } - this.monitoringData = monitoringData; return this.staticData == null || this.staticData == ClientStaticData.NULL_DATA From 0bdb91e4c14e83f0b2bd0c439aa563d6ddbd1e63 Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 31 May 2023 15:19:36 +0200 Subject: [PATCH 14/21] adapted tomcat config --- src/main/resources/config/application.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/config/application.properties b/src/main/resources/config/application.properties index 0079b89b..c7f7849f 100644 --- a/src/main/resources/config/application.properties +++ b/src/main/resources/config/application.properties @@ -15,10 +15,10 @@ server.servlet.context-path=/ # Tomcat server.tomcat.max-threads=2000 server.tomcat.accept-count=300 -socket.soKeepAlive=true -socket.performanceConnectionTime=1 -socket.performanceLatency=2 -socket.performanceBandwidth=0 +server.tomcat.socket.soKeepAlive=true +server.tomcat.socket.performanceConnectionTime=1 +server.tomcat.socket.performanceLatency=2 +server.tomcat.socket.performanceBandwidth=0 server.tomcat.uri-encoding=UTF-8 ### encoding From 2a16813cd7a0ce360ff241f9f4657ebaa128542d Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 31 May 2023 15:21:45 +0200 Subject: [PATCH 15/21] test tomcat config --- .../java/ch/ethz/seb/sebserver/SEBServer.java | 49 +++++++++---------- .../resources/config/application.properties | 2 + 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/SEBServer.java b/src/main/java/ch/ethz/seb/sebserver/SEBServer.java index d3018c4d..f8bd28e1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/SEBServer.java +++ b/src/main/java/ch/ethz/seb/sebserver/SEBServer.java @@ -10,13 +10,10 @@ package ch.ethz.seb.sebserver; import org.apache.catalina.connector.Connector; import org.apache.commons.lang3.BooleanUtils; -import org.apache.coyote.http11.AbstractHttp11Protocol; -import org.apache.coyote.http11.Http11NioProtocol; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; -import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -87,29 +84,29 @@ public class SEBServer { return firewall; } - @Bean - public WebServerFactoryCustomizer tomcatCustomizer() { - return (tomcat) -> tomcat.addConnectorCustomizers((connector) -> { - if (connector.getProtocolHandler() instanceof AbstractHttp11Protocol) { - System.out.println("*************** tomcatCustomizer"); - final AbstractHttp11Protocol protocolHandler = (AbstractHttp11Protocol) connector - .getProtocolHandler(); - protocolHandler.setKeepAliveTimeout(60000); - protocolHandler.setMaxKeepAliveRequests(3000); - protocolHandler.setUseKeepAliveResponseHeader(true); - protocolHandler.setMinSpareThreads(200); - protocolHandler.setProcessorCache(-1); - protocolHandler.setTcpNoDelay(true); - protocolHandler.setThreadPriority(Thread.NORM_PRIORITY + 1); - protocolHandler.setMaxConnections(2000); - if (protocolHandler instanceof Http11NioProtocol) { - System.out.println("*************** Http11NioProtocol"); - ((Http11NioProtocol) protocolHandler).setPollerThreadPriority(Thread.MAX_PRIORITY); - } - - } - }); - } +// @Bean +// public WebServerFactoryCustomizer tomcatCustomizer() { +// return (tomcat) -> tomcat.addConnectorCustomizers((connector) -> { +// if (connector.getProtocolHandler() instanceof AbstractHttp11Protocol) { +// System.out.println("*************** tomcatCustomizer"); +// final AbstractHttp11Protocol protocolHandler = (AbstractHttp11Protocol) connector +// .getProtocolHandler(); +// protocolHandler.setKeepAliveTimeout(60000); +// protocolHandler.setMaxKeepAliveRequests(3000); +// protocolHandler.setUseKeepAliveResponseHeader(true); +// protocolHandler.setMinSpareThreads(200); +// protocolHandler.setProcessorCache(-1); +// protocolHandler.setTcpNoDelay(true); +// protocolHandler.setThreadPriority(Thread.NORM_PRIORITY + 1); +// protocolHandler.setMaxConnections(2000); +// if (protocolHandler instanceof Http11NioProtocol) { +// System.out.println("*************** Http11NioProtocol"); +// ((Http11NioProtocol) protocolHandler).setPollerThreadPriority(Thread.MAX_PRIORITY); +// } +// +// } +// }); +// } private Connector redirectConnector(final Environment env) { final String sslPort = env.getRequiredProperty("server.port"); diff --git a/src/main/resources/config/application.properties b/src/main/resources/config/application.properties index c7f7849f..57b27078 100644 --- a/src/main/resources/config/application.properties +++ b/src/main/resources/config/application.properties @@ -19,6 +19,8 @@ server.tomcat.socket.soKeepAlive=true server.tomcat.socket.performanceConnectionTime=1 server.tomcat.socket.performanceLatency=2 server.tomcat.socket.performanceBandwidth=0 +server.tomcat.keepAliveTimeout(20000); +server.tomcat.maxKeepAliveRequests(3000); server.tomcat.uri-encoding=UTF-8 ### encoding From b344ee22e5f544f812660ee43382ce729663b579 Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 31 May 2023 20:30:12 +0200 Subject: [PATCH 16/21] ping with executor --- .../gbl/async/AsyncServiceSpringConfig.java | 9 ---- .../sebserver/webservice/WebserviceInfo.java | 2 +- .../weblayer/api/ExamAPI_V1_Controller.java | 45 ++++++++++--------- .../config/application-ws.properties | 2 +- 4 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java index dcf25d9d..4595c63f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java @@ -74,15 +74,6 @@ public class AsyncServiceSpringConfig implements AsyncConfigurer { return executor; } -// @Bean -// public ThreadPoolTaskScheduler threadPoolTaskScheduler() { -// final ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); -// threadPoolTaskScheduler.setPoolSize(5); -// threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(false); -// threadPoolTaskScheduler.setThreadNamePrefix("SEB-Server-BgTask-"); -// return threadPoolTaskScheduler; -// } - @Override public Executor getAsyncExecutor() { return threadPoolTaskExecutor(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java index 3be4b33c..340cca32 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java @@ -101,7 +101,7 @@ public class WebserviceInfo { this.distributedUpdateInterval = environment.getProperty( "sebserver.webservice.distributed.updateInterval", Long.class, - 3000L); + 2000L); this.activeProfiles = new HashSet<>(Arrays.asList(environment.getActiveProfiles())); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java index 04a16689..d59a404f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java @@ -325,29 +325,32 @@ public class ExamAPI_V1_Controller { method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public void ping(final HttpServletRequest request, final HttpServletResponse response) { + public CompletableFuture ping(final HttpServletRequest request, final HttpServletResponse response) { + return CompletableFuture.runAsync( + () -> { + final String connectionToken = request.getHeader(API.EXAM_API_SEB_CONNECTION_TOKEN); + //final String pingNumString = request.getParameter(API.EXAM_API_PING_NUMBER); + final String instructionConfirm = request.getParameter(API.EXAM_API_PING_INSTRUCTION_CONFIRM); - final String connectionToken = request.getHeader(API.EXAM_API_SEB_CONNECTION_TOKEN); - //final String pingNumString = request.getParameter(API.EXAM_API_PING_NUMBER); - final String instructionConfirm = request.getParameter(API.EXAM_API_PING_INSTRUCTION_CONFIRM); + final String instruction = this.sebClientSessionService + .notifyPing( + connectionToken, + 0, + 0, + instructionConfirm); - final String instruction = this.sebClientSessionService - .notifyPing( - connectionToken, - 0, - 0, - instructionConfirm); - - if (instruction == null) { - response.setStatus(HttpStatus.NO_CONTENT.value()); - } else { - try { - response.setStatus(HttpStatus.OK.value()); - response.getOutputStream().write(instruction.getBytes(StandardCharsets.UTF_8)); - } catch (final IOException e) { - log.error("Failed to send instruction as response: {}", connectionToken, e); - } - } + if (instruction == null) { + response.setStatus(HttpStatus.NO_CONTENT.value()); + } else { + try { + response.setStatus(HttpStatus.OK.value()); + response.getOutputStream().write(instruction.getBytes(StandardCharsets.UTF_8)); + } catch (final IOException e) { + log.error("Failed to send instruction as response: {}", connectionToken, e); + } + } + }, + this.executor); } @RequestMapping( diff --git a/src/main/resources/config/application-ws.properties b/src/main/resources/config/application-ws.properties index acafe27a..0cab6d27 100644 --- a/src/main/resources/config/application-ws.properties +++ b/src/main/resources/config/application-ws.properties @@ -40,7 +40,7 @@ sebserver.webservice.internalSecret=${sebserver.password} ### webservice networking sebserver.webservice.forceMaster=false sebserver.webservice.distributed=false -sebserver.webservice.distributed.updateInterval=3000 +sebserver.webservice.distributed.updateInterval=2000 sebserver.webservice.http.external.scheme=https sebserver.webservice.http.external.servername= sebserver.webservice.http.external.port= From 8cab729401895a7b6124b4789042125f803ad377 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 1 Jun 2023 09:03:22 +0200 Subject: [PATCH 17/21] SEBSERV-445 code cleanup --- .../java/ch/ethz/seb/sebserver/SEBServer.java | 24 ----------- .../webservice/WebserviceConfig.java | 24 +++++++++++ .../session/SEBClientSessionService.java | 3 +- .../session/impl/ExamSessionServiceImpl.java | 1 + ...e.java => SEBClientEventBatchService.java} | 21 +++------- ...ce.java => SEBClientPingBatchService.java} | 25 +++-------- .../impl/SEBClientSessionServiceImpl.java | 39 +++--------------- .../DistributedIndicatorValueService.java | 7 ++-- .../weblayer/api/ExamAPI_V1_Controller.java | 41 ++++++++----------- .../api/ExamMonitoringController.java | 4 -- .../api/exam/SebConnectionTest.java | 4 +- .../services/ClientEventServiceTest.java | 4 +- 12 files changed, 65 insertions(+), 132 deletions(-) rename src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/{SEBClientEventBatchStore.java => SEBClientEventBatchService.java} (90%) rename src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/{SEBClientPingService.java => SEBClientPingBatchService.java} (77%) diff --git a/src/main/java/ch/ethz/seb/sebserver/SEBServer.java b/src/main/java/ch/ethz/seb/sebserver/SEBServer.java index f8bd28e1..da12f52d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/SEBServer.java +++ b/src/main/java/ch/ethz/seb/sebserver/SEBServer.java @@ -84,30 +84,6 @@ public class SEBServer { return firewall; } -// @Bean -// public WebServerFactoryCustomizer tomcatCustomizer() { -// return (tomcat) -> tomcat.addConnectorCustomizers((connector) -> { -// if (connector.getProtocolHandler() instanceof AbstractHttp11Protocol) { -// System.out.println("*************** tomcatCustomizer"); -// final AbstractHttp11Protocol protocolHandler = (AbstractHttp11Protocol) connector -// .getProtocolHandler(); -// protocolHandler.setKeepAliveTimeout(60000); -// protocolHandler.setMaxKeepAliveRequests(3000); -// protocolHandler.setUseKeepAliveResponseHeader(true); -// protocolHandler.setMinSpareThreads(200); -// protocolHandler.setProcessorCache(-1); -// protocolHandler.setTcpNoDelay(true); -// protocolHandler.setThreadPriority(Thread.NORM_PRIORITY + 1); -// protocolHandler.setMaxConnections(2000); -// if (protocolHandler instanceof Http11NioProtocol) { -// System.out.println("*************** Http11NioProtocol"); -// ((Http11NioProtocol) protocolHandler).setPollerThreadPriority(Thread.MAX_PRIORITY); -// } -// -// } -// }); -// } - private Connector redirectConnector(final Environment env) { final String sslPort = env.getRequiredProperty("server.port"); final String httpPort = env.getProperty("sebserver.ssl.redirect.html.port", "80"); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceConfig.java index c1f79837..7055077c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceConfig.java @@ -29,4 +29,28 @@ public class WebserviceConfig { return aes256jnCryptor; } +// @Bean +// public WebServerFactoryCustomizer tomcatCustomizer() { +// return (tomcat) -> tomcat.addConnectorCustomizers((connector) -> { +// if (connector.getProtocolHandler() instanceof AbstractHttp11Protocol) { +// System.out.println("*************** tomcatCustomizer"); +// final AbstractHttp11Protocol protocolHandler = (AbstractHttp11Protocol) connector +// .getProtocolHandler(); +// protocolHandler.setKeepAliveTimeout(60000); +// protocolHandler.setMaxKeepAliveRequests(3000); +// protocolHandler.setUseKeepAliveResponseHeader(true); +// protocolHandler.setMinSpareThreads(200); +// protocolHandler.setProcessorCache(-1); +// protocolHandler.setTcpNoDelay(true); +// protocolHandler.setThreadPriority(Thread.NORM_PRIORITY + 1); +// protocolHandler.setMaxConnections(2000); +// if (protocolHandler instanceof Http11NioProtocol) { +// System.out.println("*************** Http11NioProtocol"); +// ((Http11NioProtocol) protocolHandler).setPollerThreadPriority(Thread.MAX_PRIORITY); +// } +// +// } +// }); +// } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java index ae10241b..1817b413 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java @@ -28,11 +28,10 @@ public interface SEBClientSessionService { /** Notify a ping for a certain client connection. * * @param connectionToken the connection token - * @param timestamp the ping time-stamp * @param pingNumber the ping number * @param instructionConfirm instruction confirm sent by the SEB client or null * @return SEB instruction if available */ - String notifyPing(String connectionToken, long timestamp, int pingNumber, String instructionConfirm); + String notifyPing(String connectionToken, int pingNumber, String instructionConfirm); /** Notify a SEB client event for live indication and storing to database. * diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java index d8d89ff9..754ce2e7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java @@ -378,6 +378,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { @Override public ClientConnectionDataInternal getConnectionDataInternal(final String connectionToken) { + // TODO do we really need to synchronize here? synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { return this.examSessionCacheService.getClientConnection(connectionToken); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java similarity index 90% rename from src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java rename to src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java index 0f04e487..1a29bb0b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchStore.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java @@ -44,9 +44,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificati @Lazy @Component @WebServiceProfile -public class SEBClientEventBatchStore { +public class SEBClientEventBatchService { - private static final Logger log = LoggerFactory.getLogger(SEBClientEventBatchStore.class); + private static final Logger log = LoggerFactory.getLogger(SEBClientEventBatchService.class); private final SEBClientNotificationService sebClientNotificationService; private final SqlSessionFactory sqlSessionFactory; @@ -57,7 +57,7 @@ public class SEBClientEventBatchStore { private final SqlSessionTemplate sqlSessionTemplate; private final ClientEventRecordMapper clientEventMapper; - public SEBClientEventBatchStore( + public SEBClientEventBatchService( final SEBClientNotificationService sebClientNotificationService, final SqlSessionFactory sqlSessionFactory, final PlatformTransactionManager transactionManager, @@ -94,14 +94,12 @@ public class SEBClientEventBatchStore { @Scheduled( fixedDelayString = "${sebserver.webservice.api.exam.session.event.batch.interval:1000}", - initialDelay = 1000) + initialDelay = 100) public void processEvents() { - final long startTime = Utils.getMillisecondsNow(); - final int size = this.eventDataQueue.size(); if (size > 1000) { - log.warn("******* There are more then 1000 SEB client logs in the waiting queue: {}", size); + log.warn("-----> There are more then 1000 SEB client logs in the waiting queue: {}", size); } try { @@ -130,15 +128,6 @@ public class SEBClientEventBatchStore { this.sqlSessionTemplate.flushStatements(); - if (log.isTraceEnabled()) { - log.trace("Processing {} SEB events tuck: {}", - this.events.size(), - Utils.getMillisecondsNow() - startTime); - } - // TODO just for debugging - System.out.println("***** Processing " + this.events.size() + " SEB events tuck: " - + (Utils.getMillisecondsNow() - startTime)); - } catch (final Exception e) { log.error("Failed to process SEB events from eventDataQueue: ", e); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java similarity index 77% rename from src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java rename to src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java index 8fb35fd0..815aad01 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java @@ -27,9 +27,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructio @Lazy @Component @WebServiceProfile -public class SEBClientPingService { +public class SEBClientPingBatchService { - private static final Logger log = LoggerFactory.getLogger(SEBClientPingService.class); + private static final Logger log = LoggerFactory.getLogger(SEBClientPingBatchService.class); private final ExamSessionCacheService examSessionCacheService; private final SEBClientInstructionService sebClientInstructionService; @@ -37,7 +37,7 @@ public class SEBClientPingService { private final Map pings = new ConcurrentHashMap<>(); private final Map instructions = new ConcurrentHashMap<>(); - public SEBClientPingService( + public SEBClientPingBatchService( final ExamSessionCacheService examSessionCacheService, final SEBClientInstructionService sebClientInstructionService) { @@ -45,19 +45,15 @@ public class SEBClientPingService { this.sebClientInstructionService = sebClientInstructionService; } - @Scheduled( - fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:500}", - initialDelay = 1000) + @Scheduled(fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:500}") public void processPings() { if (this.pings.isEmpty()) { return; } - final long startTime = Utils.getMillisecondsNow(); - final int size = this.pings.size(); if (size > 1000) { - log.warn("******* There are more then 1000 SEB client logs in the waiting queue: {}", size); + log.warn("----> There are more then 1000 SEB client logs in the waiting queue: {}", size); } try { @@ -68,17 +64,6 @@ public class SEBClientPingService { this.pings.remove(cid), Utils.getMillisecondsNow())); -// pp.entrySet() -// .stream() -// .forEach(entry -> processPing(entry.getKey(), entry.getValue(), startTime)); - - if (log.isTraceEnabled()) { - log.trace("****** Processing {} SEB pings tuck: {}", Utils.getMillisecondsNow() - startTime); - } - // TODO just for debugging - System.out.println("***** Processing " + size + " SEB pings tuck: " - + (Utils.getMillisecondsNow() - startTime)); - } catch (final Exception e) { log.error("Failed to process SEB pings from pingDataQueue: ", e); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java index 69fc9279..49ba71c4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java @@ -31,7 +31,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientVersionService; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchStore.EventData; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchService.EventData; @Lazy @Service @@ -42,29 +42,27 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { private final ClientConnectionDAO clientConnectionDAO; private final ExamSessionService examSessionService; - private final ExamSessionCacheService examSessionCacheService; - private final SEBClientEventBatchStore sebClientEventBatchStore; + private final SEBClientEventBatchService sebClientEventBatchStore; private final SEBClientInstructionService sebInstructionService; private final ClientIndicatorFactory clientIndicatorFactory; private final InternalClientConnectionDataFactory internalClientConnectionDataFactory; private final SecurityKeyService securityKeyService; private final SEBClientVersionService sebClientVersionService; - private final SEBClientPingService sebClientPingService; + private final SEBClientPingBatchService sebClientPingService; public SEBClientSessionServiceImpl( final ClientConnectionDAO clientConnectionDAO, final ExamSessionService examSessionService, - final SEBClientEventBatchStore sebClientEventBatchStore, + final SEBClientEventBatchService sebClientEventBatchStore, final SEBClientInstructionService sebInstructionService, final ClientIndicatorFactory clientIndicatorFactory, final InternalClientConnectionDataFactory internalClientConnectionDataFactory, final SecurityKeyService securityKeyService, final SEBClientVersionService sebClientVersionService, - final SEBClientPingService sebClientPingService) { + final SEBClientPingBatchService sebClientPingService) { this.clientConnectionDAO = clientConnectionDAO; this.examSessionService = examSessionService; - this.examSessionCacheService = examSessionService.getExamSessionCacheService(); this.sebClientEventBatchStore = sebClientEventBatchStore; this.sebInstructionService = sebInstructionService; this.clientIndicatorFactory = clientIndicatorFactory; @@ -114,29 +112,12 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { @Override public String notifyPing( final String connectionToken, - final long timestamp, final int pingNumber, final String instructionConfirm) { return this.sebClientPingService.notifyPing(connectionToken, instructionConfirm); } -// @Override -// public String notifyPing( -// final String connectionToken, -// final long timestamp, -// final int pingNumber, -// final String instructionConfirm) { -// -// processPing(connectionToken, timestamp, pingNumber); -// -// if (instructionConfirm != null) { -// this.sebInstructionService.confirmInstructionDone(connectionToken, instructionConfirm); -// } -// -// return this.sebInstructionService.getInstructionJSON(connectionToken); -// } - @Override public final void notifyClientEvent(final String connectionToken, final String jsonBody) { this.sebClientEventBatchStore.accept(connectionToken, jsonBody); @@ -155,16 +136,6 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { this.internalClientConnectionDataFactory.getGroupIds(clientConnection))); } -// private void processPing(final String connectionToken, final long timestamp, final int pingNumber) { -// -// final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService -// .getClientConnection(connectionToken); -// -// if (activeClientConnection != null) { -// activeClientConnection.notifyPing(timestamp); -// } -// } - private void missingPingUpdate(final ClientConnectionDataInternal connection) { if (connection.pingIndicator.changeOnIncident()) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedIndicatorValueService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedIndicatorValueService.java index 44bf983b..6f257b10 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedIndicatorValueService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/DistributedIndicatorValueService.java @@ -344,10 +344,8 @@ public class DistributedIndicatorValueService implements DisposableBean { } /** Update last ping time on persistent storage asynchronously within a defines thread pool with no - * waiting queue to skip further ping updates if all update threads are busy - * - * TODO: we need a better handling strategy here. - * Try to apply a batch update and store the pings in a concurrent hash map **/ + * waiting queue to skip further ping updates if all update threads are busy **/ + // TODO: we need a better handling strategy here. Try to apply a batch update managed by SEBClientPingBatchService void updatePingAsync(final Long pingRecord) { try { this.indicatorValueUpdateExecutor @@ -363,6 +361,7 @@ public class DistributedIndicatorValueService implements DisposableBean { /** Update indicator value on persistent storage asynchronously within a defined thread pool with no * waiting queue to skip further indicator value updates if all update threads are busy **/ + // TODO: we need a better handling strategy here. Try to apply a batch update managed by SEBClientEventBatchStore boolean updateIndicatorValueAsync(final Long pk, final Long value) { try { this.indicatorValueUpdateExecutor diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java index d59a404f..db47e307 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java @@ -325,32 +325,25 @@ public class ExamAPI_V1_Controller { method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public CompletableFuture ping(final HttpServletRequest request, final HttpServletResponse response) { - return CompletableFuture.runAsync( - () -> { - final String connectionToken = request.getHeader(API.EXAM_API_SEB_CONNECTION_TOKEN); - //final String pingNumString = request.getParameter(API.EXAM_API_PING_NUMBER); - final String instructionConfirm = request.getParameter(API.EXAM_API_PING_INSTRUCTION_CONFIRM); + public void ping(final HttpServletRequest request, final HttpServletResponse response) { - final String instruction = this.sebClientSessionService - .notifyPing( - connectionToken, - 0, - 0, - instructionConfirm); + final String connectionToken = request.getHeader(API.EXAM_API_SEB_CONNECTION_TOKEN); + //final String pingNumString = request.getParameter(API.EXAM_API_PING_NUMBER); + final String instructionConfirm = request.getParameter(API.EXAM_API_PING_INSTRUCTION_CONFIRM); - if (instruction == null) { - response.setStatus(HttpStatus.NO_CONTENT.value()); - } else { - try { - response.setStatus(HttpStatus.OK.value()); - response.getOutputStream().write(instruction.getBytes(StandardCharsets.UTF_8)); - } catch (final IOException e) { - log.error("Failed to send instruction as response: {}", connectionToken, e); - } - } - }, - this.executor); + final String instruction = this.sebClientSessionService + .notifyPing(connectionToken, 0, instructionConfirm); + + if (instruction == null) { + response.setStatus(HttpStatus.NO_CONTENT.value()); + } else { + try { + response.setStatus(HttpStatus.OK.value()); + response.getOutputStream().write(instruction.getBytes(StandardCharsets.UTF_8)); + } catch (final IOException e) { + log.error("Failed to send instruction as response: {}", connectionToken, e); + } + } } @RequestMapping( diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java index b5b70eab..52ef0191 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java @@ -314,8 +314,6 @@ public class ExamMonitoringController { name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER, required = false) final String hiddenClientGroups) { - final long now = Utils.getMillisecondsNow(); - final Exam runningExam = checkPrivileges(institutionId, examId); final MonitoringSEBConnectionData monitoringSEBConnectionData = this.examSessionService @@ -342,8 +340,6 @@ public class ExamMonitoringController { Collections.emptyList()); } - System.out.println("%%%%%%%% --> monitoring tuck: " + (Utils.getMillisecondsNow() - now)); - return monitoringFullPageData; } diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java index fb2062d9..f7baae0f 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java @@ -42,7 +42,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ClientConnectionDataInternal; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ExamSessionCacheService; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchStore; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchService; @Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql", "classpath:data-test-additional.sql" }) public class SebConnectionTest extends ExamAPIIntegrationTester { @@ -58,7 +58,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { @Autowired private LmsAPIService lmsAPIService; @Autowired - private SEBClientEventBatchStore sebClientEventBatchStore; + private SEBClientEventBatchService sebClientEventBatchStore; @Before public void init() { diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java index 8625cc83..075099b6 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/ClientEventServiceTest.java @@ -32,7 +32,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientSessionService; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchStore; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientEventBatchService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.AbstractLogIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.AbstractLogLevelCountIndicator; @@ -48,7 +48,7 @@ public class ClientEventServiceTest extends AdministrationAPIIntegrationTester { @Autowired private SEBClientSessionService sebClientSessionService; @Autowired - private SEBClientEventBatchStore sebClientEventBatchStore; + private SEBClientEventBatchService sebClientEventBatchStore; // @Autowired // @Qualifier(AsyncServiceSpringConfig.EXAM_API_EXECUTOR_BEAN_NAME) // private Executor executor; From 2c197665f0935b104072c1378775e08a445d84bd Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 1 Jun 2023 12:43:41 +0200 Subject: [PATCH 18/21] fixed task scheduler config --- .../sebserver/gbl/async/AsyncServiceSpringConfig.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java index 4595c63f..50a20ce3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java @@ -17,6 +17,7 @@ import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; @Configuration @EnableAsync @@ -74,6 +75,15 @@ public class AsyncServiceSpringConfig implements AsyncConfigurer { return executor; } + @Bean + public ThreadPoolTaskScheduler threadPoolTaskScheduler() { + final ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); + threadPoolTaskScheduler.setPoolSize(5); + threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(false); + threadPoolTaskScheduler.setThreadNamePrefix("SEB-Server-BgTask-"); + return threadPoolTaskScheduler; + } + @Override public Executor getAsyncExecutor() { return threadPoolTaskExecutor(); From 45e01dc0ddb35c53e65f9f11cbfd98394d9c9b72 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 1 Jun 2023 14:29:15 +0200 Subject: [PATCH 19/21] re-added synchronization to look if it has effect --- .../impl/SEBClientEventBatchService.java | 20 ++++++++++++++----- .../impl/SEBClientPingBatchService.java | 9 +++++++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java index 1a29bb0b..7a262eca 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java @@ -160,10 +160,16 @@ public class SEBClientEventBatchService { return eventData; } - System.out.println("******* storeNotifications: " + eventData); + ClientConnectionDataInternal clientConnection = null; + synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { + clientConnection = this.examSessionCacheService + .getClientConnection(eventData.connectionToken); + } - final ClientConnectionDataInternal clientConnection = this.examSessionCacheService - .getClientConnection(eventData.connectionToken); + if (clientConnection == null) { + log.error("Failed to get ClientConnectionDataInternal for: {}", eventData.connectionToken); + return null; + } final Pair typeAndPlainText = ClientNotification.extractTypeAndPlainText(eventData.event.text); @@ -200,8 +206,12 @@ public class SEBClientEventBatchService { private ClientEventRecord toEventRecord(final EventData eventData) { try { - final ClientConnectionDataInternal clientConnection = this.examSessionCacheService - .getClientConnection(eventData.connectionToken); + + ClientConnectionDataInternal clientConnection = null; + synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { + clientConnection = this.examSessionCacheService + .getClientConnection(eventData.connectionToken); + } if (clientConnection == null) { log.warn("Failed to retrieve ClientConnection for token {}. Skip this event", diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java index 815aad01..44a86a95 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java @@ -91,11 +91,16 @@ public class SEBClientPingBatchService { return; } - final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService - .getClientConnection(connectionToken); + ClientConnectionDataInternal activeClientConnection = null; + synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { + activeClientConnection = this.examSessionCacheService + .getClientConnection(connectionToken); + } if (activeClientConnection != null) { activeClientConnection.notifyPing(timestamp); + } else { + log.error("Failed to get ClientConnectionDataInternal for: {}", connectionToken); } if (instructionConfirm != StringUtils.EMPTY) { From 5dc85f9e735af80cced97aa2d3658b7bdf267ae2 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 1 Jun 2023 14:43:54 +0200 Subject: [PATCH 20/21] no sync in ping and event --- .../session/impl/SEBClientEventBatchService.java | 16 ++++++++-------- .../session/impl/SEBClientPingBatchService.java | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java index 7a262eca..a7674577 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java @@ -161,10 +161,10 @@ public class SEBClientEventBatchService { } ClientConnectionDataInternal clientConnection = null; - synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { - clientConnection = this.examSessionCacheService - .getClientConnection(eventData.connectionToken); - } + //synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { + clientConnection = this.examSessionCacheService + .getClientConnection(eventData.connectionToken); + //} if (clientConnection == null) { log.error("Failed to get ClientConnectionDataInternal for: {}", eventData.connectionToken); @@ -208,10 +208,10 @@ public class SEBClientEventBatchService { try { ClientConnectionDataInternal clientConnection = null; - synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { - clientConnection = this.examSessionCacheService - .getClientConnection(eventData.connectionToken); - } + //synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { + clientConnection = this.examSessionCacheService + .getClientConnection(eventData.connectionToken); + //} if (clientConnection == null) { log.warn("Failed to retrieve ClientConnection for token {}. Skip this event", diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java index 44a86a95..f0e69a81 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java @@ -92,10 +92,10 @@ public class SEBClientPingBatchService { } ClientConnectionDataInternal activeClientConnection = null; - synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { - activeClientConnection = this.examSessionCacheService - .getClientConnection(connectionToken); - } + //synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { + activeClientConnection = this.examSessionCacheService + .getClientConnection(connectionToken); + //} if (activeClientConnection != null) { activeClientConnection.notifyPing(timestamp); From 933ddaa4d354d8b9877a09889c2a7b5c61c46b0a Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 1 Jun 2023 15:47:29 +0200 Subject: [PATCH 21/21] removed sync again (seems to has no effect) --- .../session/impl/SEBClientEventBatchService.java | 10 ++-------- .../session/impl/SEBClientPingBatchService.java | 5 +---- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java index a7674577..087808e2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientEventBatchService.java @@ -160,11 +160,8 @@ public class SEBClientEventBatchService { return eventData; } - ClientConnectionDataInternal clientConnection = null; - //synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { - clientConnection = this.examSessionCacheService + final ClientConnectionDataInternal clientConnection = this.examSessionCacheService .getClientConnection(eventData.connectionToken); - //} if (clientConnection == null) { log.error("Failed to get ClientConnectionDataInternal for: {}", eventData.connectionToken); @@ -207,11 +204,8 @@ public class SEBClientEventBatchService { private ClientEventRecord toEventRecord(final EventData eventData) { try { - ClientConnectionDataInternal clientConnection = null; - //synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { - clientConnection = this.examSessionCacheService + final ClientConnectionDataInternal clientConnection = this.examSessionCacheService .getClientConnection(eventData.connectionToken); - //} if (clientConnection == null) { log.warn("Failed to retrieve ClientConnection for token {}. Skip this event", diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java index f0e69a81..81625a41 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java @@ -91,11 +91,8 @@ public class SEBClientPingBatchService { return; } - ClientConnectionDataInternal activeClientConnection = null; - //synchronized (ExamSessionCacheService.CLIENT_CONNECTION_CREATION_LOCK) { - activeClientConnection = this.examSessionCacheService + final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService .getClientConnection(connectionToken); - //} if (activeClientConnection != null) { activeClientConnection.notifyPing(timestamp);