{"record":{"id":"55d13468ad6b1e58","repo":"apache/pulsar","slug":"pulsar-function-local-run-already-started","errorCode":null,"errorMessage":"Pulsar Function local run already started!","messagePattern":"Pulsar Function local run already started!","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/localrun/src/main/java/org/apache/pulsar/functions/LocalRunner.java","lineNumber":351,"sourceCode":"    }\n\n    private static void closeClassLoaderIfneeded(UserCodeClassLoader userCodeClassLoader) {\n        if (userCodeClassLoader != null && userCodeClassLoader.isClassLoaderCreated()) {\n            if (userCodeClassLoader.getClassLoader() instanceof Closeable) {\n                try {\n                    ((Closeable) userCodeClassLoader.getClassLoader()).close();\n                } catch (IOException e) {\n                    log.warn().exception(e).log(\"Error closing classloader\");\n                }\n            }\n        }\n    }\n\n    public void start(boolean blocking) throws Exception {\n        List<RuntimeSpawner> local = new LinkedList<>();\n        synchronized (this) {\n            if (!running.compareAndSet(false, true)) {\n                throw new IllegalArgumentException(\"Pulsar Function local run already started!\");\n            }\n            Runtime.getRuntime().addShutdownHook(shutdownHook);\n            FunctionDetails functionDetails = null;\n            String userCodeFile;\n            String transformFunctionFile = null;\n            int parallelism;\n            if (functionConfig != null) {\n                FunctionConfigUtils.inferMissingArguments(functionConfig, true);\n                parallelism = functionConfig.getParallelism();\n                if (functionConfig.getRuntime() == FunctionConfig.Runtime.JAVA) {\n                    userCodeFile = functionConfig.getJar();\n                    userCodeClassLoader = extractClassLoader(\n                        userCodeFile, ComponentType.FUNCTION, functionConfig.getClassName());\n                    ValidatableFunctionPackage validatableFunctionPackage =\n                            new LoadedFunctionPackage(getCurrentOrUserCodeClassLoader(),\n                                    FunctionDefinition.class);\n                    functionDetails = FunctionConfigUtils.convert(\n                        functionConfig,","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/localrun/src/main/java/org/apache/pulsar/functions/LocalRunner.java#L333-L369","documentation":"LocalRunner.start uses an AtomicBoolean 'running' with compareAndSet to guarantee a single local function run per runner instance. Calling start() on an already-started (or concurrently started) runner throws IllegalArgumentException. It is a lifecycle-guard, not an environment failure.","triggerScenarios":"Invoking LocalRunner.start() (or start(boolean)) twice on the same LocalRunner instance, or from two threads concurrently; also when calling start after a previous start that never closed the runner.","commonSituations":"Test scaffolding that starts the runner in both @BeforeEach and the test body; re-running start() after close() failed; sharing a LocalRunner across threads without coordination.","solutions":["Call start() exactly once per LocalRunner instance","Create a new LocalRunner instance instead of restarting the old one","Ensure close()/stop is invoked before any further lifecycle use, or guard with your own started flag","Synchronize or use a single thread/executor to trigger startup"],"exampleFix":"// before\nrunner.start();\nrunner.start();\n// after\nif (runnerStarted.compareAndSet(false, true)) {\n    runner.start();\n}","handlingStrategy":"try-catch","validationCode":"java.util.concurrent.atomic.AtomicBoolean started = new AtomicBoolean(false);\nif (!started.compareAndSet(false, true)) {\n    throw new IllegalStateException(\"runner already started\");\n}\nrunner.start(true);","typeGuard":null,"tryCatchPattern":"try {\n    runner.start(true);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"already started\")) {\n        // ignore: runner is already running\n    }\n}","preventionTips":["Start each LocalRunner exactly once","Wrap start in your own compareAndSet guard","Never share a LocalRunner across threads without synchronization"],"tags":["lifecycle","concurrency","illegal-argument"],"backgroundTag":"already-started","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}