various stability fixed for ping and indicator handling

This commit is contained in:
anhefti 2021-12-13 13:42:08 +01:00
parent f645192e60
commit e569b3592b
10 changed files with 184 additions and 84 deletions

View file

@ -128,6 +128,9 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
// Create an initial admin account if requested and not already in the data-base
this.adminUserInitializer.initAdminAccount();
SEBServerInit.INIT_LOGGER.info("----> log4j2.formatMsgNoLookups = {}",
this.environment.getProperty("log4j2.formatMsgNoLookups", "none"));
}
@PreDestroy

View file

@ -44,6 +44,14 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
* happened */
Result<Collection<Long>> allIdsOfInstitution(Long institutionId);
/** Get all active and running Exams for a given institution.
*
* @param institutionId the identifier of the institution
* @return Result refer to a collection of all active and running exams of the given institution or refer to an
* error if
* happened */
Result<Collection<Long>> allIdsOfRunning(final Long institutionId);
/** Get all institution ids for that a specified exam for given quiz id already exists
*
* @param quizId The quiz or external identifier of the exam (LMS)

View file

@ -132,6 +132,18 @@ public interface RemoteProctoringRoomDAO {
* @return Result refer to active break-out rooms or to an error when happened */
Result<Collection<String>> getConnectionsInBreakoutRooms(Long examId);
/** Mark a specified collecting room as opened or closed by a proctor.
*
* @param roomId The collecting room identifier
* @param isOpen mark open or not */
void setCollectingRoomOpenFlag(Long roomId, boolean isOpen);
/** Use this to update the current room size of for a proctoring collecting room
* by its real number of attached SEB connections. This can be used on error case to
* recover and set the re calc the number of participants in a room
*
* @param remoteProctoringRoomId The proctoring room identifier
* @return The newly calculated number of participants in the room. */
Result<Long> updateRoomSize(Long remoteProctoringRoomId);
}

View file

@ -186,7 +186,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
@Override
@Transactional
public Result<Collection<ClientConnectionRecord>> getAllConnectionIdsForRoomUpdateActive() {
return Result.tryCatch(() -> {
return Result.<Collection<ClientConnectionRecord>> tryCatch(() -> {
final Collection<ClientConnectionRecord> records = this.clientConnectionRecordMapper
.selectByExample()
.where(ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomUpdate, isNotEqualTo(0))
@ -210,7 +211,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
.execute();
return records;
});
})
.onError(TransactionHandler::rollback);
}
private ClientConnectionRecord createProctoringRoomUpdateRecord(final int remoteProctoringRoomUpdate) {
@ -250,7 +252,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
@Override
@Transactional
public Result<Collection<ClientConnectionRecord>> getAllConnectionIdsForRoomUpdateInactive() {
return Result.tryCatch(() -> {
return Result.<Collection<ClientConnectionRecord>> tryCatch(() -> {
final Collection<ClientConnectionRecord> records = this.clientConnectionRecordMapper
.selectByExample()
.where(ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomUpdate, isNotEqualTo(0))
@ -274,13 +276,17 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
.execute();
return records;
});
})
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public void setNeedsRoomUpdate(final Long connectionId) {
final ClientConnectionRecord updateRecord = createProctoringRoomUpdateRecord(1);
final ClientConnectionRecord updateRecord = new ClientConnectionRecord(
connectionId, null, null, null, null, null,
null, null, null, null, null, null, null,
1);
this.clientConnectionRecordMapper.updateByPrimaryKeySelective(updateRecord);
}
@ -362,8 +368,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
data.vdiPairToken,
null,
millisecondsNow,
data.remoteProctoringRoomId,
BooleanUtils.toIntegerObject(data.remoteProctoringRoomUpdate));
null,
null);
this.clientConnectionRecordMapper.updateByPrimaryKeySelective(updateRecord);
return this.clientConnectionRecordMapper.selectByPrimaryKey(data.id);
@ -386,7 +392,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
null, null, null, null, null, null,
roomId,
0));
});
})
.onError(TransactionHandler::rollback);
}
@Override
@ -399,7 +406,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
null, null, null, null, null, null,
null,
1));
});
})
.onError(TransactionHandler::rollback);
}
@Override
@ -426,7 +434,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
} else {
throw new ResourceNotFoundException(EntityType.CLIENT_CONNECTION, String.valueOf(connectionId));
}
});
})
.onError(TransactionHandler::rollback);
}
@Override
@ -467,7 +476,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
@Override
@Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
return Result.tryCatch(() -> {
return Result.<Collection<EntityKey>> tryCatch(() -> {
final List<Long> ids = extractListOfPKs(all);
if (ids == null || ids.isEmpty()) {
@ -514,7 +523,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
return ids.stream()
.map(id -> new EntityKey(id, EntityType.CLIENT_CONNECTION))
.collect(Collectors.toList());
});
})
.onError(TransactionHandler::rollback);
}
@Override

View file

@ -144,6 +144,7 @@ public class ExamDAOImpl implements ExamDAO {
}
@Override
@Transactional(readOnly = true)
public Result<Collection<Long>> allInstitutionIdsByQuizId(final String quizId) {
return Result.tryCatch(() -> {
return this.examRecordMapper.selectByExample()
@ -242,6 +243,7 @@ public class ExamDAOImpl implements ExamDAO {
}
@Override
@Transactional
public Result<Exam> updateState(final Long examId, final ExamStatus status, final String updateId) {
return recordById(examId)
.map(examRecord -> {
@ -727,6 +729,23 @@ public class ExamDAOImpl implements ExamDAO {
.execute());
}
@Override
@Transactional(readOnly = true)
public Result<Collection<Long>> allIdsOfRunning(final Long institutionId) {
return Result.tryCatch(() -> this.examRecordMapper.selectIdsByExample()
.where(
ExamRecordDynamicSqlSupport.institutionId,
isEqualTo(institutionId))
.and(
ExamRecordDynamicSqlSupport.active,
isEqualToWhenPresent(BooleanUtils.toIntegerObject(true)))
.and(
ExamRecordDynamicSqlSupport.status,
isEqualTo(ExamStatus.RUNNING.name()))
.build()
.execute());
}
private Result<Collection<EntityDependency>> allIdsOfInstitution(final EntityKey institutionKey) {
return Result.tryCatch(() -> toDependencies(
this.examRecordMapper.selectByExample()

View file

@ -29,9 +29,12 @@ import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.RemoteProctoringRoomRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.RemoteProctoringRoomRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.RemoteProctoringRoomRecord;
@ -47,16 +50,17 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
private static final Logger log = LoggerFactory.getLogger(RemoteProctoringRoomDAOImpl.class);
private static final Object RESERVE_ROOM_LOCK = new Object();
private final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper;
private final ClientConnectionRecordMapper clientConnectionRecordMapper;
private final AdditionalAttributesDAO additionalAttributesDAO;
protected RemoteProctoringRoomDAOImpl(
final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper,
final ClientConnectionRecordMapper clientConnectionRecordMapper,
final AdditionalAttributesDAO additionalAttributesDAO) {
this.remoteProctoringRoomRecordMapper = remoteProctoringRoomRecordMapper;
this.clientConnectionRecordMapper = clientConnectionRecordMapper;
this.additionalAttributesDAO = additionalAttributesDAO;
}
@ -196,7 +200,8 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
.map(room -> {
this.remoteProctoringRoomRecordMapper.deleteByPrimaryKey(room.id);
return new EntityKey(room.id, EntityType.REMOTE_PROCTORING_ROOM);
});
})
.onError(TransactionHandler::rollback);
}
@Override
@ -239,7 +244,8 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
.deleteByPrimaryKey(roomId);
return new EntityKey(roomId, EntityType.REMOTE_PROCTORING_ROOM);
});
})
.onError(TransactionHandler::rollback);
}
@Override
@ -286,23 +292,21 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
final Function<Long, Result<NewRoom>> newRoomFunction) {
return Result.tryCatch(() -> {
synchronized (RESERVE_ROOM_LOCK) {
final Optional<RemoteProctoringRoomRecord> room =
this.remoteProctoringRoomRecordMapper.selectByExample()
.where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
.and(RemoteProctoringRoomRecordDynamicSqlSupport.townhallRoom, isEqualTo(0))
.and(RemoteProctoringRoomRecordDynamicSqlSupport.breakOutConnections, isNull())
.build()
.execute()
.stream()
.filter(r -> r.getSize() < roomMaxSize)
.findFirst();
final Optional<RemoteProctoringRoomRecord> room =
this.remoteProctoringRoomRecordMapper.selectByExample()
.where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
.and(RemoteProctoringRoomRecordDynamicSqlSupport.townhallRoom, isEqualTo(0))
.and(RemoteProctoringRoomRecordDynamicSqlSupport.breakOutConnections, isNull())
.build()
.execute()
.stream()
.filter(r -> r.getSize() < roomMaxSize)
.findFirst();
if (room.isPresent()) {
return updateCollectingRoom(room.get());
} else {
return createNewCollectingRoom(examId, newRoomFunction);
}
if (room.isPresent()) {
return updateCollectingRoom(room.get());
} else {
return createNewCollectingRoom(examId, newRoomFunction);
}
})
.map(this::toDomainModel)
@ -313,18 +317,21 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
@Transactional
public Result<RemoteProctoringRoom> releasePlaceInCollectingRoom(final Long examId, final Long roomId) {
return Result.tryCatch(() -> {
synchronized (RESERVE_ROOM_LOCK) {
final RemoteProctoringRoomRecord record = this.remoteProctoringRoomRecordMapper
.selectByPrimaryKey(roomId);
final RemoteProctoringRoomRecord record = this.remoteProctoringRoomRecordMapper
.selectByPrimaryKey(roomId);
final RemoteProctoringRoomRecord remoteProctoringRoomRecord = new RemoteProctoringRoomRecord(
record.getId(), null, null,
record.getSize() - 1, null, null, null, null, null);
this.remoteProctoringRoomRecordMapper.updateByPrimaryKeySelective(remoteProctoringRoomRecord);
return this.remoteProctoringRoomRecordMapper
.selectByPrimaryKey(remoteProctoringRoomRecord.getId());
final int size = record.getSize() - 1;
if (size < 0) {
throw new IllegalStateException("Room size mismatch, cannot be negative");
}
final RemoteProctoringRoomRecord remoteProctoringRoomRecord = new RemoteProctoringRoomRecord(
record.getId(), null, null,
size, null, null, null, null, null);
this.remoteProctoringRoomRecordMapper.updateByPrimaryKeySelective(remoteProctoringRoomRecord);
return this.remoteProctoringRoomRecordMapper
.selectByPrimaryKey(remoteProctoringRoomRecord.getId());
})
.map(this::toDomainModel)
.onError(TransactionHandler::rollback);
@ -372,7 +379,34 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
BooleanUtils.toStringTrueFalse(isOpen))
.onError(error -> log.error("Failed to set open flag for proctoring room: {} : {}",
roomId,
error.getMessage()));
error.getMessage()))
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<Long> updateRoomSize(final Long remoteProctoringRoomId) {
return Result.tryCatch(() -> {
final Long size = this.clientConnectionRecordMapper
.countByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomId,
isEqualTo(remoteProctoringRoomId))
.and(
ClientConnectionRecordDynamicSqlSupport.status,
isEqualTo(ConnectionStatus.ACTIVE.name()))
.build()
.execute();
this.remoteProctoringRoomRecordMapper.updateByPrimaryKeySelective(
new RemoteProctoringRoomRecord(
remoteProctoringRoomId, null, null,
size.intValue(), null, null,
null, null, null));
return size;
})
.onError(TransactionHandler::rollback);
}
private RemoteProctoringRoom toDomainModel(final RemoteProctoringRoomRecord record) {

View file

@ -109,7 +109,7 @@ class ExamSessionControlTask implements DisposableBean {
this.examDAO.releaseAgedLocks();
}
@Scheduled(fixedRateString = "${sebserver.webservice.api.seb.lostping.update:5000}")
@Scheduled(fixedDelayString = "${sebserver.webservice.api.seb.lostping.update:5000}")
public void examSessionUpdateTask() {
this.sebClientConnectionService.updatePingEvents();

View file

@ -231,7 +231,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
@Override
public Result<Collection<Exam>> getRunningExamsForInstitution(final Long institutionId) {
return this.examDAO.allIdsOfInstitution(institutionId)
return this.examDAO.allIdsOfRunning(institutionId)
.map(col -> col.stream()
.map(this::getRunningExam)
.filter(Result::hasValue)

View file

@ -63,7 +63,11 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
super.init(indicatorDefinition, connectionId, active, cachingEnabled);
this.currentValue = computeValueAt(DateTimeUtils.currentTimeMillis());
final long now = DateTimeUtils.currentTimeMillis();
this.currentValue = computeValueAt(now);
if (Double.isNaN(this.currentValue)) {
this.currentValue = now;
}
try {
indicatorDefinition

View file

@ -52,6 +52,8 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
private static final Logger log = LoggerFactory.getLogger(ExamProctoringRoomServiceImpl.class);
private static final Object RESERVE_ROOM_LOCK = new Object();
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
private final ClientConnectionDAO clientConnectionDAO;
private final ExamAdminService examAdminService;
@ -280,53 +282,61 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
}
private void assignToCollectingRoom(final ClientConnectionRecord cc) {
try {
synchronized (RESERVE_ROOM_LOCK) {
try {
if (cc.getRemoteProctoringRoomId() == null) {
if (cc.getRemoteProctoringRoomId() == null) {
final RemoteProctoringRoom proctoringRoom = getProctoringRoom(
cc.getExamId(),
cc.getConnectionToken());
final RemoteProctoringRoom proctoringRoom = getProctoringRoom(
cc.getExamId(),
cc.getConnectionToken());
if (log.isDebugEnabled()) {
log.debug("Assigning new SEB client to proctoring room: {}, connection: {}",
proctoringRoom.id,
cc);
if (log.isDebugEnabled()) {
log.debug("Assigning new SEB client to proctoring room: {}, connection: {}",
proctoringRoom.id,
cc);
}
this.clientConnectionDAO
.assignToProctoringRoom(
cc.getId(),
cc.getConnectionToken(),
proctoringRoom.id)
.getOrThrow();
applyProcotringInstruction(cc)
.getOrThrow();
}
this.clientConnectionDAO
.assignToProctoringRoom(
cc.getId(),
cc.getConnectionToken(),
proctoringRoom.id)
.getOrThrow();
} catch (final Exception e) {
log.error("Failed to assign connection to collecting room: {}", cc, e);
}
applyProcotringInstruction(cc)
.getOrThrow();
} catch (final Exception e) {
log.error("Failed to assign connection to collecting room: {}", cc, e);
}
}
private void removeFromRoom(final ClientConnectionRecord cc) {
try {
synchronized (RESERVE_ROOM_LOCK) {
try {
this.remoteProctoringRoomDAO.releasePlaceInCollectingRoom(
cc.getExamId(),
cc.getRemoteProctoringRoomId());
this.remoteProctoringRoomDAO.releasePlaceInCollectingRoom(
cc.getExamId(),
cc.getRemoteProctoringRoomId());
this.cleanupBreakOutRooms(cc);
this.cleanupBreakOutRooms(cc);
this.clientConnectionDAO
.removeFromProctoringRoom(cc.getId(), cc.getConnectionToken())
.onError(error -> log.error("Failed to remove client connection form room: ", error))
.getOrThrow();
this.clientConnectionDAO
.removeFromProctoringRoom(cc.getId(), cc.getConnectionToken())
.onError(error -> log.error("Failed to remove client connection from room: ", error))
.getOrThrow();
} catch (final Exception e) {
log.error("Failed to update client connection for proctoring room: ", e);
this.clientConnectionDAO.setNeedsRoomUpdate(cc.getId());
} catch (final Exception e) {
log.error("Failed to update client connection for proctoring room: ", e);
try {
this.remoteProctoringRoomDAO.updateRoomSize(cc.getRemoteProctoringRoomId());
} catch (final Exception ee) {
log.error("Failed to update room size: ", ee);
}
}
}
}
@ -632,7 +642,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.getOrThrow();
try {
sendJoinInstruction(
registerJoinInstruction(
examId,
connectionToken,
roomConnection,
@ -688,7 +698,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
error.getMessage()))
.get();
if (proctoringConnection != null) {
sendJoinInstruction(
registerJoinInstruction(
proctoringSettings.examId,
connectionToken,
proctoringConnection,
@ -753,7 +763,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
remoteProctoringRoom.subject)
.getOrThrow();
sendJoinInstruction(
registerJoinInstruction(
proctoringSettings.examId,
clientConnection.clientConnection.connectionToken,
proctoringConnection,
@ -765,14 +775,14 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
}
}
private void sendJoinInstruction(
private void registerJoinInstruction(
final Long examId,
final String connectionToken,
final ProctoringRoomConnection proctoringConnection,
final ExamProctoringService examProctoringService) {
if (log.isDebugEnabled()) {
log.debug("Send proctoring join instruction to connection: {}, room: {}",
log.debug("Register proctoring join instruction for connection: {}, room: {}",
connectionToken,
proctoringConnection.roomName);
}