@Override public void emitTuples() { if (currentWindowId <= idempotentStorageManager.getLargestRecoveryWindow()) { return; } if (inputStream == null) { try { if (currentFile != null && offset > 0) { // open file resets offset to 0 so this a way around it. int tmpOffset = offset; if (fs.exists(new Path(currentFile))) { this.inputStream = openFile(new Path(currentFile)); offset = tmpOffset; skipCount = tmpOffset; } else { currentFile = null; offset = 0; skipCount = 0; } } else if (!unfinishedFiles.isEmpty()) { retryFailedFile(unfinishedFiles.poll()); } else if (!pendingFiles.isEmpty()) { String newPathString = pendingFiles.iterator().next(); pendingFiles.remove(newPathString); if (fs.exists(new Path(newPathString))) this.inputStream = openFile(new Path(newPathString)); } else if (!failedFiles.isEmpty()) { retryFailedFile(failedFiles.poll()); } else { scanDirectory(); } } catch (IOException ex) { failureHandling(ex); } } if (inputStream != null) { int startOffset = offset; String file = currentFile; // current file is reset to null when closed. try { int counterForTuple = 0; while (counterForTuple++ < emitBatchSize) { T line = readEntity(); if (line == null) { LOG.info("done reading file ({} entries).", offset); closeFile(inputStream); break; } // If skipCount is non zero, then failed file recovery is going on, skipCount is // used to prevent already emitted records from being emitted again during recovery. // When failed file is open, skipCount is set to the last read offset for that file. // if (skipCount == 0) { offset++; emit(line); } else { skipCount--; } } } catch (IOException e) { failureHandling(e); } // Only when something was emitted from the file then we record it for entry. if (offset > startOffset) { currentWindowRecoveryState.add(new RecoveryEntry(file, startOffset, offset)); } } }
protected void replay(long windowId) { // This operator can partition itself dynamically. When that happens a file can be re-hashed // to a different partition than the previous one. In order to handle this, the partition loads // all the recovery data for a window and then processes only those files which would be hashed // to it in the current run. try { Map<Integer, Object> recoveryDataPerOperator = idempotentStorageManager.load(windowId); for (Object recovery : recoveryDataPerOperator.values()) { @SuppressWarnings("unchecked") LinkedList<RecoveryEntry> recoveryData = (LinkedList<RecoveryEntry>) recovery; for (RecoveryEntry recoveryEntry : recoveryData) { if (scanner.acceptFile(recoveryEntry.file)) { // The operator may have continued processing the same file in multiple windows. // So the recovery states of subsequent windows will have an entry for that file however // the offset changes. // In this case we continue reading from previously opened stream. if (currentFile == null || !(currentFile.equals(recoveryEntry.file) && offset == recoveryEntry.startOffset)) { if (inputStream != null) { closeFile(inputStream); } processedFiles.add(recoveryEntry.file); // removing the file from failed and unfinished queues and pending set Iterator<FailedFile> failedFileIterator = failedFiles.iterator(); while (failedFileIterator.hasNext()) { FailedFile ff = failedFileIterator.next(); if (ff.path.equals(recoveryEntry.file) && ff.offset == recoveryEntry.startOffset) { failedFileIterator.remove(); break; } } Iterator<FailedFile> unfinishedFileIterator = unfinishedFiles.iterator(); while (unfinishedFileIterator.hasNext()) { FailedFile ff = unfinishedFileIterator.next(); if (ff.path.equals(recoveryEntry.file) && ff.offset == recoveryEntry.startOffset) { unfinishedFileIterator.remove(); break; } } if (pendingFiles.contains(recoveryEntry.file)) { pendingFiles.remove(recoveryEntry.file); } inputStream = retryFailedFile(new FailedFile(recoveryEntry.file, recoveryEntry.startOffset)); while (offset < recoveryEntry.endOffset) { T line = readEntity(); offset++; emit(line); } } else { while (offset < recoveryEntry.endOffset) { T line = readEntity(); offset++; emit(line); } } } } } } catch (IOException e) { throw new RuntimeException("replay", e); } }