public final void runEventProcessingLoop() {
        if (!this.processingThread.compareAndSet(null, Thread.currentThread())) {
            throw new IllegalStateException("A thread is already processing events for this watcher.");
        }

        while (true) {
            // poll for a key from the watch service
            WatchKey key;
            try {
                key = this.service.take();
            } catch (InterruptedException | ClosedWatchServiceException e) {
                break;
            }

            // find the directory the key is watching
            Path directory = this.keys.get(key);
            if (directory == null) {
                key.cancel();
                continue;
            }

            // process each watch event the key has
            for (WatchEvent<?> ev : key.pollEvents()) {
                @SuppressWarnings("unchecked")
                WatchEvent<Path> event = (WatchEvent<Path>) ev;

                Path context = event.context();

                // ignore contexts with a name count of zero
                if (context == null || context.getNameCount() == 0) {
                    continue;
                }

                // resolve the context of the event against the directory being watched
                Path file = directory.resolve(context);

                // if the file is a regular file, send the event on to be processed
                if (Files.isRegularFile(file)) {
                    processEvent(event, file);
                }

                // handle recursive directory creation
                if (this.autoRegisterNewSubDirectories && event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
                    try {
                        if (Files.isDirectory(file, LinkOption.NOFOLLOW_LINKS)) {
                            registerRecursively(file);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

            // reset the key
            boolean valid = key.reset();
            if (!valid) {
                this.keys.remove(key);
            }
        }

        this.processingThread.compareAndSet(Thread.currentThread(), null);
    }
Processes {@link WatchEvent}s from the watch service until it is closed, or until
the thread is interrupted.
common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/watcher/AbstractFileWatcher.java