Minor improvement in instruction handling (distributed setup)

This commit is contained in:
anhefti 2021-04-01 09:37:46 +02:00
parent 804911e3b8
commit 749cbea287
3 changed files with 46 additions and 9 deletions

View file

@ -38,6 +38,12 @@ public interface ClientInstructionDAO {
* @return Result refer to all instructions that are younger then one minute or to an error when happened */ * @return Result refer to all instructions that are younger then one minute or to an error when happened */
Result<Collection<ClientInstructionRecord>> getAllActive(); Result<Collection<ClientInstructionRecord>> getAllActive();
/** Get all active instructions for a specified connection token
*
* @param connectionToken the connection token
* @return Collection of all active instructions for specified connection token */
Result<Collection<ClientInstructionRecord>> getAllActive(String connectionToken);
/** Deletes the specified instruction form the data base /** Deletes the specified instruction form the data base
* *
* @param id the identifier (PK) if the ClientInstruction to delete * @param id the identifier (PK) if the ClientInstruction to delete

View file

@ -64,6 +64,22 @@ public class ClientInstructionDAOImpl implements ClientInstructionDAO {
}); });
} }
@Override
public Result<Collection<ClientInstructionRecord>> getAllActive(final String connectionToken) {
return Result.tryCatch(() -> {
final long millisNowMinusOneMinute = DateTime.now(DateTimeZone.UTC).minusMinutes(1).getMillis();
return this.clientInstructionRecordMapper
.selectByExample()
.where(ClientInstructionRecordDynamicSqlSupport.timestamp,
SqlBuilder.isGreaterThanOrEqualTo(millisNowMinusOneMinute))
.and(
ClientInstructionRecordDynamicSqlSupport.connectionToken,
SqlBuilder.isEqualTo(connectionToken))
.build()
.execute();
});
}
@Override @Override
@Transactional @Transactional
public Result<Void> delete(final Long id) { public Result<Void> delete(final Long id) {

View file

@ -113,7 +113,7 @@ public class SEBClientInstructionServiceImpl implements SEBClientInstructionServ
final String attributesString = this.jsonMapper.writeValueAsString(attributes); final String attributesString = this.jsonMapper.writeValueAsString(attributes);
this.clientInstructionDAO this.clientInstructionDAO
.insert(examId, type, attributesString, connectionToken, needsConfirm) .insert(examId, type, attributesString, connectionToken, needsConfirm)
.map(this::putToCacheIfAbsent) .map(this::putToCache)
.onError(error -> log.error("Failed to register instruction: {}", error.getMessage())) .onError(error -> log.error("Failed to register instruction: {}", error.getMessage()))
.getOrThrow(); .getOrThrow();
} catch (final Exception e) { } catch (final Exception e) {
@ -141,19 +141,20 @@ public class SEBClientInstructionServiceImpl implements SEBClientInstructionServ
connectionTokens connectionTokens
.stream() .stream()
.filter(activeConnections::contains) .filter(activeConnections::contains)
.map(token -> this.clientInstructionDAO.insert(examId, type, attributesString, token, needsConfirm)) .map(token -> this.clientInstructionDAO
.insert(examId, type, attributesString, token, needsConfirm))
.map(result -> result.get( .map(result -> result.get(
error -> log.error("Failed to register instruction: {}", error.getMessage()), error -> log.error("Failed to register instruction: {}", error.getMessage()),
() -> null)) () -> null))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.forEach(this::putToCacheIfAbsent); .forEach(this::putToCache);
}); });
} }
@Override @Override
public String getInstructionJSON(final String connectionToken) { public String getInstructionJSON(final String connectionToken) {
refreshCache(); refreshCache(connectionToken);
if (this.instructions.isEmpty()) { if (this.instructions.isEmpty()) {
return null; return null;
} }
@ -242,35 +243,49 @@ public class SEBClientInstructionServiceImpl implements SEBClientInstructionServ
} }
} }
private void refreshCache() { private void refreshCache(final String connectionToken) {
if (!this.webserviceInfo.isDistributed()) { if (!this.webserviceInfo.isDistributed() && !this.instructions.isEmpty()) {
return; return;
} }
final long currentTimeMillis = System.currentTimeMillis(); final long currentTimeMillis = System.currentTimeMillis();
if (currentTimeMillis - this.lastRefresh > Constants.SECOND_IN_MILLIS) { if (currentTimeMillis - this.lastRefresh > Constants.SECOND_IN_MILLIS) {
this.lastRefresh = currentTimeMillis; this.lastRefresh = currentTimeMillis;
loadInstructions(connectionToken)
loadInstructions()
.onError(error -> log.error( .onError(error -> log.error(
"Failed load instructions from persistent storage and to refresh cache: ", "Failed load instructions from persistent storage and to refresh cache: ",
error)); error));
} }
} }
private Result<Void> loadInstructions(final String connectionToken) {
return Result.tryCatch(() -> this.clientInstructionDAO.getAllActive(connectionToken)
.getOrThrow()
.forEach(this::putToCacheIfAbsent));
}
private Result<Void> loadInstructions() { private Result<Void> loadInstructions() {
return Result.tryCatch(() -> this.clientInstructionDAO.getAllActive() return Result.tryCatch(() -> this.clientInstructionDAO.getAllActive()
.getOrThrow() .getOrThrow()
.forEach(this::putToCacheIfAbsent)); .forEach(this::putToCacheIfAbsent));
} }
// NOTE: In a distributed setup we only fill the cache from persistent storage
// whereas in a none distributed setup we can put the instruction directly in the cache
// and store the instruction into persistent only for recovering reasons.
private ClientInstructionRecord putToCache(final ClientInstructionRecord instruction) {
if (!this.webserviceInfo.isDistributed()) {
return putToCacheIfAbsent(instruction);
}
return instruction;
}
private ClientInstructionRecord putToCacheIfAbsent(final ClientInstructionRecord instruction) { private ClientInstructionRecord putToCacheIfAbsent(final ClientInstructionRecord instruction) {
final SizedArrayNonBlockingQueue<ClientInstructionRecord> queue = this.instructions.computeIfAbsent( final SizedArrayNonBlockingQueue<ClientInstructionRecord> queue = this.instructions.computeIfAbsent(
instruction.getConnectionToken(), instruction.getConnectionToken(),
key -> new SizedArrayNonBlockingQueue<>(INSTRUCTION_QUEUE_MAX_SIZE)); key -> new SizedArrayNonBlockingQueue<>(INSTRUCTION_QUEUE_MAX_SIZE));
if (queue.contains(instruction)) { if (queue.contains(instruction)) {
log.warn("Instruction alread in the queue: {}", instruction);
return instruction; return instruction;
} }