_checkSnapshotValidNoCache(snapshot, callback) {
		/** @type {number | undefined} */
		let startTime;
		if (snapshot.hasStartTime()) {
			startTime = snapshot.startTime;
		}
		let jobs = 1;
		const jobDone = () => {
			if (--jobs === 0) {
				this._snapshotCache.set(snapshot, true);
				callback(null, true);
			}
		};
		const invalid = () => {
			if (jobs > 0) {
				// large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
				jobs = -100000000;
				this._snapshotCache.set(snapshot, false);
				callback(null, false);
			}
		};
		/**
		 * @param {string} path path
		 * @param {WebpackError} err err
		 */
		const invalidWithError = (path, err) => {
			if (this._remainingLogs > 0) {
				this._log(path, "error occurred: %s", err);
			}
			invalid();
		};
		/**
		 * @param {string} path file path
		 * @param {string | null} current current hash
		 * @param {string | null} snap snapshot hash
		 * @returns {boolean} true, if ok
		 */
		const checkHash = (path, current, snap) => {
			if (current !== snap) {
				// If hash differ it's invalid
				if (this._remainingLogs > 0) {
					this._log(path, "hashes differ (%s != %s)", current, snap);
				}
				return false;
			}
			return true;
		};
		/**
		 * @param {string} path file path
		 * @param {boolean} current current entry
		 * @param {boolean} snap entry from snapshot
		 * @returns {boolean} true, if ok
		 */
		const checkExistence = (path, current, snap) => {
			if (!current !== !snap) {
				// If existence of item differs
				// it's invalid
				if (this._remainingLogs > 0) {
					this._log(
						path,
						current ? "it didn't exist before" : "it does no longer exist"
					);
				}
				return false;
			}
			return true;
		};
		/**
		 * @param {string} path file path
		 * @param {FileSystemInfoEntry | null} c current entry
		 * @param {FileSystemInfoEntry | null} s entry from snapshot
		 * @param {boolean} log log reason
		 * @returns {boolean} true, if ok
		 */
		const checkFile = (path, c, s, log = true) => {
			if (c === s) return true;
			if (!checkExistence(path, Boolean(c), Boolean(s))) return false;
			if (c) {
				// For existing items only
				if (typeof startTime === "number" && c.safeTime > startTime) {
					// If a change happened after starting reading the item
					// this may no longer be valid
					if (log && this._remainingLogs > 0) {
						this._log(
							path,
							"it may have changed (%d) after the start time of the snapshot (%d)",
							c.safeTime,
							startTime
						);
					}
					return false;
				}
				const snap = /** @type {FileSystemInfoEntry} */ (s);
				if (snap.timestamp !== undefined && c.timestamp !== snap.timestamp) {
					// If we have a timestamp (it was a file or symlink) and it differs from current timestamp
					// it's invalid
					if (log && this._remainingLogs > 0) {
						this._log(
							path,
							"timestamps differ (%d != %d)",
							c.timestamp,
							snap.timestamp
						);
					}
					return false;
				}
			}
			return true;
		};
		/**
		 * @param {string} path file path
		 * @param {ResolvedContextFileSystemInfoEntry | null} c current entry
		 * @param {ResolvedContextFileSystemInfoEntry | null} s entry from snapshot
		 * @param {boolean} log log reason
		 * @returns {boolean} true, if ok
		 */
		const checkContext = (path, c, s, log = true) => {
			if (c === s) return true;
			if (!checkExistence(path, Boolean(c), Boolean(s))) return false;
			if (c) {
				// For existing items only
				if (typeof startTime === "number" && c.safeTime > startTime) {
					// If a change happened after starting reading the item
					// this may no longer be valid
					if (log && this._remainingLogs > 0) {
						this._log(
							path,
							"it may have changed (%d) after the start time of the snapshot (%d)",
							c.safeTime,
							startTime
						);
					}
					return false;
				}
				const snap = /** @type {ResolvedContextFileSystemInfoEntry} */ (s);
				if (
					snap.timestampHash !== undefined &&
					c.timestampHash !== snap.timestampHash
				) {
					// If we have a timestampHash (it was a directory) and it differs from current timestampHash
					// it's invalid
					if (log && this._remainingLogs > 0) {
						this._log(
							path,
							"timestamps hashes differ (%s != %s)",
							c.timestampHash,
							snap.timestampHash
						);
					}
					return false;
				}
			}
			return true;
		};
		if (snapshot.hasChildren()) {
			/**
			 * @param {(WebpackError | null)=} err err
			 * @param {boolean=} result result
			 * @returns {void}
			 */
			const childCallback = (err, result) => {
				if (err || !result) return invalid();
				jobDone();
			};
			for (const child of /** @type {Children} */ (snapshot.children)) {
				const cache = this._snapshotCache.get(child);
				if (cache !== undefined) {
					this._statTestedChildrenCached++;
					/* istanbul ignore else */
					if (typeof cache === "boolean") {
						if (cache === false) {
							invalid();
							return;
						}
					} else {
						jobs++;
						cache.push(childCallback);
					}
				} else {
					this._statTestedChildrenNotCached++;
					jobs++;
					this._checkSnapshotValidNoCache(child, childCallback);
				}
			}
		}
		if (snapshot.hasFileTimestamps()) {
			const fileTimestamps =
				/** @type {FileTimestamps} */
				(snapshot.fileTimestamps);
			this._statTestedEntries += fileTimestamps.size;
			for (const [path, ts] of fileTimestamps) {
				const cache = this._fileTimestamps.get(path);
				if (cache !== undefined) {
					if (cache !== "ignore" && !checkFile(path, cache, ts)) {
						invalid();
						return;
					}
				} else {
					jobs++;
					this.fileTimestampQueue.add(path, (err, entry) => {
						if (err) return invalidWithError(path, err);
						if (
							!checkFile(
								path,
								/** @type {FileSystemInfoEntry | null} */ (entry),
								ts
							)
						) {
							invalid();
						} else {
							jobDone();
						}
					});
				}
			}
		}
		/**
		 * @param {string} path file path
		 * @param {string | null} hash hash
		 */
		const processFileHashSnapshot = (path, hash) => {
			const cache = this._fileHashes.get(path);
			if (cache !== undefined) {
				if (cache !== "ignore" && !checkHash(path, cache, hash)) {
					invalid();
				}
			} else {
				jobs++;
				this.fileHashQueue.add(path, (err, entry) => {
					if (err) return invalidWithError(path, err);
					if (!checkHash(path, /** @type {string} */ (entry), hash)) {
						invalid();
					} else {
						jobDone();
					}
				});
			}
		};
		if (snapshot.hasFileHashes()) {
			const fileHashes = /** @type {FileHashes} */ (snapshot.fileHashes);
			this._statTestedEntries += fileHashes.size;
			for (const [path, hash] of fileHashes) {
				processFileHashSnapshot(path, hash);
			}
		}
		if (snapshot.hasFileTshs()) {
			const fileTshs = /** @type {FileTshs} */ (snapshot.fileTshs);
			this._statTestedEntries += fileTshs.size;
			for (const [path, tsh] of fileTshs) {
				if (typeof tsh === "string") {
					processFileHashSnapshot(path, tsh);
				} else {
					const cache = this._fileTimestamps.get(path);
					if (cache !== undefined) {
						if (cache === "ignore" || !checkFile(path, cache, tsh, false)) {
							processFileHashSnapshot(path, tsh && tsh.hash);
						}
					} else {
						jobs++;
						this.fileTimestampQueue.add(path, (err, entry) => {
							if (err) return invalidWithError(path, err);
							if (
								!checkFile(
									path,
									/** @type {FileSystemInfoEntry | null} */
									(entry),
									tsh,
									false
								)
							) {
								processFileHashSnapshot(path, tsh && tsh.hash);
							}
							jobDone();
						});
					}
				}
			}
		}
		if (snapshot.hasContextTimestamps()) {
			const contextTimestamps =
				/** @type {ContextTimestamps} */
				(snapshot.contextTimestamps);
			this._statTestedEntries += contextTimestamps.size;
			for (const [path, ts] of contextTimestamps) {
				const cache = this._contextTimestamps.get(path);
				if (cache === "ignore") continue;
				let resolved;
				if (
					cache !== undefined &&
					(resolved = getResolvedTimestamp(cache)) !== undefined
				) {
					if (!checkContext(path, resolved, ts)) {
						invalid();
						return;
					}
				} else {
					jobs++;
					/**
					 * @param {(WebpackError | null)=} err error
					 * @param {ResolvedContextTimestamp=} entry entry
					 * @returns {void}
					 */
					const callback = (err, entry) => {
						if (err) return invalidWithError(path, err);
						if (
							!checkContext(
								path,
								/** @type {ResolvedContextFileSystemInfoEntry | null} */
								(entry),
								ts
							)
						) {
							invalid();
						} else {
							jobDone();
						}
					};
					if (cache !== undefined) {
						this._resolveContextTimestamp(
							/** @type {ContextFileSystemInfoEntry} */
							(cache),
							callback
						);
					} else {
						this.getContextTimestamp(path, callback);
					}
				}
			}
		}
		/**
		 * @param {string} path path
		 * @param {string | null} hash hash
		 */
		const processContextHashSnapshot = (path, hash) => {
			const cache = this._contextHashes.get(path);
			let resolved;
			if (
				cache !== undefined &&
				(resolved = getResolvedHash(cache)) !== undefined
			) {
				if (!checkHash(path, resolved, hash)) {
					invalid();
				}
			} else {
				jobs++;
				/**
				 * @param {(WebpackError | null)=} err err
				 * @param {string=} entry entry
				 * @returns {void}
				 */
				const callback = (err, entry) => {
					if (err) return invalidWithError(path, err);
					if (!checkHash(path, /** @type {string} */ (entry), hash)) {
						invalid();
					} else {
						jobDone();
					}
				};
				if (cache !== undefined) {
					this._resolveContextHash(cache, callback);
				} else {
					this.getContextHash(path, callback);
				}
			}
		};
		if (snapshot.hasContextHashes()) {
			const contextHashes =
				/** @type {ContextHashes} */
				(snapshot.contextHashes);
			this._statTestedEntries += contextHashes.size;
			for (const [path, hash] of contextHashes) {
				processContextHashSnapshot(path, hash);
			}
		}
		if (snapshot.hasContextTshs()) {
			const contextTshs = /** @type {ContextTshs} */ (snapshot.contextTshs);
			this._statTestedEntries += contextTshs.size;
			for (const [path, tsh] of contextTshs) {
				if (typeof tsh === "string") {
					processContextHashSnapshot(path, tsh);
				} else {
					const cache = this._contextTimestamps.get(path);
					if (cache === "ignore") continue;
					let resolved;
					if (
						cache !== undefined &&
						(resolved = getResolvedTimestamp(cache)) !== undefined
					) {
						if (
							!checkContext(
								path,
								/** @type {ResolvedContextFileSystemInfoEntry | null} */
								(resolved),
								tsh,
								false
							)
						) {
							processContextHashSnapshot(path, tsh && tsh.hash);
						}
					} else {
						jobs++;
						/**
						 * @param {(WebpackError | null)=} err error
						 * @param {ResolvedContextTimestamp=} entry entry
						 * @returns {void}
						 */
						const callback = (err, entry) => {
							if (err) return invalidWithError(path, err);
							if (
								!checkContext(
									path,
									// TODO: test with `"ignore"`
									/** @type {ResolvedContextFileSystemInfoEntry | null} */
									(entry),
									tsh,
									false
								)
							) {
								processContextHashSnapshot(path, tsh && tsh.hash);
							}
							jobDone();
						};
						if (cache !== undefined) {
							this._resolveContextTimestamp(
								/** @type {ContextFileSystemInfoEntry} */
								(cache),
								callback
							);
						} else {
							this.getContextTimestamp(path, callback);
						}
					}
				}
			}
		}
		if (snapshot.hasMissingExistence()) {
			const missingExistence =
				/** @type {MissingExistence} */
				(snapshot.missingExistence);
			this._statTestedEntries += missingExistence.size;
			for (const [path, existence] of missingExistence) {
				const cache = this._fileTimestamps.get(path);
				if (cache !== undefined) {
					if (
						cache !== "ignore" &&
						!checkExistence(path, Boolean(cache), Boolean(existence))
					) {
						invalid();
						return;
					}
				} else {
					jobs++;
					this.fileTimestampQueue.add(path, (err, entry) => {
						if (err) return invalidWithError(path, err);
						if (!checkExistence(path, Boolean(entry), Boolean(existence))) {
							invalid();
						} else {
							jobDone();
						}
					});
				}
			}
		}
		if (snapshot.hasManagedItemInfo()) {
			const managedItemInfo =
				/** @type {ManagedItemInfo} */
				(snapshot.managedItemInfo);
			this._statTestedEntries += managedItemInfo.size;
			for (const [path, info] of managedItemInfo) {
				const cache = this._managedItems.get(path);
				if (cache !== undefined) {
					if (!checkHash(path, cache, info)) {
						invalid();
						return;
					}
				} else {
					jobs++;
					this.managedItemQueue.add(path, (err, entry) => {
						if (err) return invalidWithError(path, err);
						if (!checkHash(path, /** @type {string} */ (entry), info)) {
							invalid();
						} else {
							jobDone();
						}
					});
				}
			}
		}
		jobDone();

		// if there was an async action
		// try to join multiple concurrent request for this snapshot
		if (jobs > 0) {
			const callbacks = [callback];
			callback = (err, result) => {
				for (const callback of callbacks) callback(err, result);
			};
			this._snapshotCache.set(snapshot, callbacks);
		}
	}
@private
@param {Snapshot} snapshot the snapshot made
@param {CheckSnapshotValidCallback} callback callback function
@returns {void}