apache/pulsar · error · IllegalStateException

FunctionArchive is already closed

Error message

FunctionArchive is already closed

What it means

FunctionArchive lazily loads the function package (FunctionFilePackage) from the archive and guards it with a closed flag. getFunctionPackage() throws this IllegalStateException when the archive has been closed (e.g. by reload/eviction logic that closes NAR archives), so the underlying package can no longer be accessed or built from this instance.

Source

Thrown at pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/functions/FunctionArchive.java:76

            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        } else {
            this.archiveChecksumHex = "";
        }
    }

    public Path getArchivePath() {
        return archivePath;
    }

    public String getArchiveChecksumHex() {
        return archiveChecksumHex;
    }

    public synchronized ValidatableFunctionPackage getFunctionPackage() {
        if (closed) {
            throw new IllegalStateException("FunctionArchive is already closed");
        }
        if (functionPackage == null) {
            functionPackage = new FunctionFilePackage(archivePath.toFile(), narExtractionDirectory, enableClassloading,
                    FunctionDefinition.class);
        }
        return functionPackage;
    }

    public FunctionDefinition getFunctionDefinition() {
        return functionDefinition;
    }

    @Override
    public synchronized void close() throws Exception {
        closed = true;
        if (functionPackage instanceof AutoCloseable) {
            ((AutoCloseable) functionPackage).close();
        }

View on GitHub (pinned to 820761864e)

Solutions

  1. Re-acquire a fresh FunctionArchive instance from the archive manager/loader instead of using the stale closed one.
  2. Serialize access so reload (close) cannot happen while another thread uses the archive, or use the latest reference lookup on every use.
  3. If you hold the archive in custom code, treat close() as terminal and re-open the archive when needed.

Example fix

// before
FunctionArchive archive = loader.load(archivePath); // kept as field
// ... later, after a reload closed it
archive.getFunctionPackage(); // IllegalStateException: FunctionArchive is already closed
// after
archive = loader.load(archivePath); // reload/re-resolve before use
archive.getFunctionPackage();
Defensive patterns

Strategy: try-catch

Validate before calling

if (archive.isClosed()) {
    archive = functionArchiveLoader.load(archivePath, narExtractionDirectory, enableClassloading); // re-resolve
}

Try / catch

try {
    ValidatableFunctionPackage pkg = archive.getFunctionPackage();
} catch (IllegalStateException e) {
    if (e.getMessage().contains("already closed")) {
        archive = functionArchiveLoader.load(archivePath, narExtractionDirectory, enableClassloading);
        ValidatableFunctionPackage pkg = archive.getFunctionPackage();
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling getFunctionPackage() (directly or via isBuiltInFunction, getBuiltinFunctionPackage, validateUpdateRequestParams) after close() was invoked on the FunctionArchive — typically after a NAR reload check decided the archive must be reloaded and closed the old instance, then a caller still holds and uses the stale reference.

Common situations: Concurrency/reload race: thread A reloads a NAR (closing the old FunctionArchive) while thread B holds the old reference and calls getFunctionPackage; caching a FunctionArchive long-term in custom code and using it after the broker/worker reloaded built-in connectors or functions.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/91c0faa963a1f712. Report an issue: GitHub.