public boolean takeRecoverySnapshot(DBChangeSet changeSet, TaskMonitor monitor)
			throws IOException, CancelledException {

		if (corruptedState) {
			throw new IOException("Corrupted BufferMgr state");
		}

		if (!(sourceFile instanceof LocalBufferFile)) {
			throw new RuntimeException("Invalid use of recovery manager");
		}

		synchronized (snapshotLock) {
			if (!canSave()) {
				// Can only handle update of existing file so we know where to put recovery files
				throw new RuntimeException(
					"Recovery snapshot only permitted for update of existing file");
			}
			if (currentCheckpointHead != null) {
				return false; // in middle of transaction
			}
			if (recoveryMgr == null) {
				recoveryMgr = new RecoveryMgr(this);
			}
			boolean success = false;
			try {
				recoveryMgr.startSnapshot(indexProvider.getIndexCount(),
					indexProvider.getFreeIndexes(), changeSet, monitor);

				//			// Save off parameter settings
				//			recoveryMgr.clearParameters();
				//			String[] names = cacheFile.getParameterNames();
				//			for (int i = 0; i < names.length; i++) {
				//				recoveryMgr.setParameter(names[i], cacheFile.getParameter(names[i]));
				//			}

				// Save off modified buffers
				int srcIndexCnt = sourceFile.getIndexCount();
				int indexCnt = indexProvider.getIndexCount();
				monitor.initialize(indexCnt);

				// Allocate working buffer
				DataBuffer buf = new DataBuffer(cacheFile.getBufferSize());

				for (int id = 0; id < indexCnt; id++) {

					monitor.checkCancelled();
					monitor.setProgress(id);

					// Check for cached buffer
					BufferNode node = null;
					boolean writeBuffer = false;

					/*
					 * Must be very careful since we would like to allow concurrent buffer
					 * 'gets' while the snapshot is in progress.  Since we must avoid locking
					 * the buffer nodes to prevent exceptions, we must copy the buffer before
					 * it is possibly reclaimed and reused by another buffer node.  Other
					 * fields concerning the snapshot mechanism within the node should remain
					 * unchanged in a read-only situation.  Buffer modifications are locked-out
					 */
					synchronized (this) {

						node = getCachedBufferNode(id);
						if (node != null) {

							if (id < srcIndexCnt && node.checkpoint == 0 && !node.modified) {
								// Buffer has not changed
								continue;
							}

							if (!node.empty) {
								if (node.buffer == null) {
									// copy buffer from disk cache
									cacheFile.get(buf, node.diskCacheIndex);
								}
								else {
									// copy buffer from cached memory buffer
									buf.copy(0, node.buffer, 0, node.buffer.length());
								}
								buf.setId(id);
								writeBuffer = true;
							}
						}
					}

					// Keep modified buffer
					if (writeBuffer) {
						recoveryMgr.putBuffer(buf, node);
					}
				}
				modifiedSinceSnapshot = false;
				success = true;
			}
			finally {
				if (recoveryMgr.isSnapshotInProgress()) {
					recoveryMgr.endSnapshot(success);
				}
				//			recoveryMgr.printStats();
			}
			return success;
		}
	}
Generate recovery snapshot of unsaved data.
@param changeSet an optional database-backed change set which reflects changes
made since the last version.
@param monitor task monitor
@return true if snapshot successful, false if
@throws IOException if IO error occurs
@throws CancelledException if task monitor is cancelled
Ghidra/Framework/DB/src/main/java/db/buffers/BufferMgr.java