progressToken); return new VisitingProgress(visitorIterator, progressToken);
public void run() { synchronized (progress.getToken()) { try { scheduledSendCreateVisitors = false; while (progress.getIterator().hasNext()) { VisitorIterator.BucketProgress bucket = progress.getIterator().getNext(); Result result = sender.send(createMessage(bucket)); if (result.isAccepted()) { progress.getIterator().update(bucket.getSuperbucket(), bucket.getProgress()); break;
log.log(LogLevel.DEBUG, sessionName + ": synchronous destroy() called"); try { synchronized (progress.getToken()) { synchronized (completionMonitor) {
@Override public void run() { synchronized (progress.getToken()) { try { assert(pendingMessageCount > 0);
private ReplyHandler createReplyHandler() { return (reply) -> { // Generally, handleReply will run in the context of the // underlying transport layer's processing thread(s), so we // schedule our own reply handling task to avoid blocking it. try { taskExecutor.submitTask(new HandleReplyTask(reply)); } catch (RejectedExecutionException e) { // We cannot reliably handle reply tasks failing to be submitted, since // the reply task performs all our internal state handling logic. As such, // we just immediately go into a failure destruction mode as soon as this // happens, in which we do not wait for any active messages to be replied // to. log.log(LogLevel.WARNING, "Visitor session '" + sessionName + "': failed to submit reply task to executor service! " + "Session cannot reliably continue; terminating it early.", e); synchronized (progress.getToken()) { transitionTo(new StateDescription(State.FAILED, "Failed to submit reply task to executor service: " + e.getMessage())); if (!done) { markSessionCompleted(); } } } }; }
private void handleErrorReply(Reply reply) { CreateVisitorMessage msg = (CreateVisitorMessage)reply.getMessage(); // Must reset bucket progress back to what it was before sending. BucketId bucket = msg.getBuckets().get(0); BucketId subProgress = msg.getBuckets().get(1); progress.getIterator().update(bucket, subProgress); String message = getErrorMessage(reply.getError(0)); log.log(LogLevel.DEBUG, sessionName + ": received error reply for bucket " + bucket + " with message '" + message + "'"); if (isFatalError(reply)) { if (params.skipBucketsOnFatalErrors()) { markBucketProgressAsFailed(bucket, subProgress, message); } else { reportVisitorError(message); transitionTo(new StateDescription(State.FAILED, message)); return; // no additional visitors will be scheduled post-failure } } if (isErrorOfType(reply, DocumentProtocol.ERROR_WRONG_DISTRIBUTION)) { handleWrongDistributionReply((WrongDistributionReply)reply); } else { if (shouldReportError(reply)) { reportVisitorError(message); } // Wait 100ms before new visitor task is executed. Will prevent // visitors from being scheduled from caller. scheduleSendCreateVisitorsIfApplicable(100, TimeUnit.MILLISECONDS); } }
public MessageBusVisitorSession(VisitorParameters visitorParameters, AsyncTaskExecutor taskExecutor, SenderFactory senderFactory, ReceiverFactory receiverFactory, RoutingTable routingTable, Clock clock) throws ParseException { this.params = visitorParameters; // TODO(vekterli): make copy? legacy impl does not copy initializeRoute(routingTable); this.sender = senderFactory.createSender(createReplyHandler(), this.params); this.receiver = receiverFactory.createReceiver(createMessageHandler(), sessionName); this.taskExecutor = taskExecutor; this.progress = createVisitingProgress(params); this.statistics = new VisitorStatistics(); this.state = new StateDescription(State.NOT_STARTED); this.clock = clock; initializeHandlers(); trace = new Trace(visitorParameters.getTraceLevel()); dataDestination = (params.getLocalDataHandler() == null ? params.getRemoteDataHandler() : receiver.getConnectionSpec()); validateSessionParameters(); // If we're already done, no need to do anything at all! if (progress.getIterator().isDone()) { markSessionCompleted(); } }
private void handleCreateVisitorReply(CreateVisitorReply reply) { CreateVisitorMessage msg = (CreateVisitorMessage)reply.getMessage(); BucketId superbucket = msg.getBuckets().get(0); BucketId subBucketProgress = reply.getLastBucket(); log.log(LogLevel.DEBUG, sessionName + ": received CreateVisitorReply for bucket " + superbucket + " with progress " + subBucketProgress); progress.getIterator().update(superbucket, subBucketProgress); params.getControlHandler().onProgress(progress.getToken()); statistics.add(reply.getVisitorStatistics()); params.getControlHandler().onVisitorStatistics(statistics); trace.getRoot().addChild(reply.getTrace().getRoot()); if (params.getDynamicallyIncreaseMaxBucketsPerVisitor() && (reply.getVisitorStatistics().getDocumentsReturned() < params.getMaxFirstPassHits() / 2.0)) { // Attempt to increase parallelism to reduce latency of visiting // Ensure new count is within [1, 128] int newMaxBuckets = Math.max(Math.min((int)(params.getMaxBucketsPerVisitor() * params.getDynamicMaxBucketsIncreaseFactor()), 128), 1); params.setMaxBucketsPerVisitor(newMaxBuckets); log.log(LogLevel.DEBUG, sessionName + ": increasing max buckets per visitor to " + params.getMaxBucketsPerVisitor()); } }
private void handleWrongDistributionReply(WrongDistributionReply reply) { try { ClusterState newState = new ClusterState(reply.getSystemState()); int stateBits = newState.getDistributionBitCount(); if (stateBits != progress.getIterator().getDistributionBitCount()) { log.log(LogLevel.DEBUG, "System state changed; now at " + stateBits + " distribution bits"); // Update the internal state of the visitor iterator. If we're increasing // the number of distribution bits, this may lead to splitting of pending // buckets. If we're decreasing, it may lead to merging of pending buckets // and potential loss of sub-bucket progress. In either way, the iterator // will not let any new buckets out before all active buckets have been // updated. progress.getIterator().setDistributionBitCount(stateBits); } } catch (Exception e) { log.log(LogLevel.ERROR, "Failed to parse new system state string: " + reply.getSystemState()); transitionTo(new StateDescription(State.FAILED, "Failed to parse cluster state '" + reply.getSystemState() + "'")); } }
private void markSessionCompleted() { // 'done' is only ever written when token mutex is held, so safe to check // outside of completionMonitor lock. assert(!done) : "Session was marked as completed more than once"; log.log(LogLevel.DEBUG, "Visitor session '" + sessionName + "' has completed"); if (params.getLocalDataHandler() != null) { params.getLocalDataHandler().onDone(); } // If skipFatalErrors is set and a fatal error did occur, fail // the session now with the first encountered error message. if (progress.getToken().containsFailedBuckets()) { transitionTo(new StateDescription(State.FAILED, progress.getToken().getFirstErrorMsg())); } // NOTE: transitioning to COMPLETED will not override a failure // state, so it's safe to always do this. transitionTo(new StateDescription(State.COMPLETED)); params.getControlHandler().onDone(state.toCompletionCode(), state.getDescription()); synchronized (completionMonitor) { done = true; completionMonitor.notifyAll(); } }
/** * A session is considered completed if one or more of the following holds true: * - All buckets have been visited (i.e. no active or pending visitors). * - Visiting has failed fatally (or has been aborted) AND there are no * active visitors remaining. 'Active' here means that we're waiting * for a reply. * - We have received sufficient number of documents (set via visitor * parameters) from the buckets already visited AND there are no * active visitors remaining. * @return true if visiting has completed, false otherwise */ private boolean visitingCompleted() { return (pendingMessageCount == 0) && (progress.getIterator().isDone() || state.failed() || enoughHitsReceived()); }
private void handleMessageProcessingException(Reply reply, Exception e, String what) { final String errorDesc = formatProcessingException(e, what); final String fullMsg = formatIdentifyingVisitorErrorString(errorDesc); log.log(LogLevel.ERROR, fullMsg, e); int errorCode; synchronized (progress.getToken()) { if (!params.skipBucketsOnFatalErrors()) { errorCode = ErrorCode.APP_FATAL_ERROR; transitionTo(new StateDescription(State.FAILED, errorDesc)); } else { errorCode = DocumentProtocol.ERROR_UNPARSEABLE; } } reply.addError(new Error(errorCode, errorDesc)); }
@Override public ProgressToken getProgress() { return progress.getToken(); }
private void markBucketProgressAsFailed(BucketId bucket, BucketId subProgress, String message) { progress.getToken().addFailedBucket(bucket, subProgress, message); progress.getIterator().update(bucket, ProgressToken.FINISHED_BUCKET); }
@Override public boolean isDone() { synchronized (progress.getToken()) { return done; } }
private boolean mayScheduleCreateVisitorsTask() { return ! (scheduledSendCreateVisitors || !progress.getIterator().hasNext() || state.failed() || enoughHitsReceived()); }
@Override public void abort() { synchronized (progress.getToken()) { transitionTo(new StateDescription(State.ABORTED, "Visitor aborted by user")); } }
public void start() { synchronized (progress.getToken()) { this.startTimeNanos = clock.monotonicNanoTime(); if (progress.getIterator().isDone()) { log.log(LogLevel.DEBUG, sessionName + ": progress token indicates " + "session is done before it could even start; no-op"); return; } transitionTo(new StateDescription(State.WORKING)); taskExecutor.submitTask(new SendCreateVisitorsTask(computeBoundedMessageTimeoutMillis(0))); } }