floci-io/floci · error · IllegalStateException

${description} file not found or not readable: ${path}

Error message

${description} file not found or not readable: ${path}

What it means

Thrown by AcknowledgeJob when the nonce is valid but the job's stored status is not 'Created'. Once a job is acknowledged its status moves to 'InProgress', so a second AcknowledgeJob for the same job is rejected with InvalidJobStateException. This mirrors AWS, where acknowledgment is a one-time transition out of the Created state.

Source

Thrown at src/main/java/io/github/hectorvent/floci/config/TlsConfigSource.java:211

                    "localhost",
                    allSans,
                    KeyAlgorithm.RSA_2048);

            Files.writeString(certFile, generated.certificatePem());
            Files.writeString(keyFile, generated.privateKeyPem());

            LOG.infov("TLS: generated self-signed certificate: {0}", certFile);

            // Persist metadata for change detection on restart
            persistMetadata(tlsDir, allSans);
        } catch (IOException e) {
            throw new IllegalStateException("Failed to write self-signed TLS certificate", e);
        }
    }

    private static void validateFileExists(String path, String description) {
        if (!Files.isReadable(Path.of(path))) {
            throw new IllegalStateException(
                    description + " file not found or not readable: " + path);
        }
    }

    /**
     * Returns {@code true} if the certificate at {@code certFile} is genuinely self-signed
     * (issuer == subject) and therefore usable as a trust anchor. Legacy Floci certs carried a
     * cosmetic Amazon issuer DN and return {@code false} here, triggering regeneration on upgrade.
     */
    private boolean isSelfSigned(Path certFile) {
        try {
            X509Certificate cert = new CertificateGenerator().parseCertificate(Files.readString(certFile));
            return cert.getIssuerX500Principal().equals(cert.getSubjectX500Principal());
        } catch (Exception e) {
            LOG.warnv("TLS: could not inspect existing certificate ({0}); regenerating", e.getMessage());
            return false;
        }
    }

View on GitHub (pinned to 62ff490619)

Solutions

  1. Treat InvalidJobStateException with 'already been acknowledged' as success in the worker's retry path (idempotency by design)
  2. Persist the acknowledged jobId locally before retrying so the worker skips re-acknowledgment
  3. Ensure only one worker instance consumes each polled job

Example fix

// before
client.acknowledgeJob(r -> r.jobId(jobId).nonce(nonce));

// after
try {
    client.acknowledgeJob(r -> r.jobId(jobId).nonce(nonce));
} catch (InvalidJobStateException alreadyAcked) {
    // first attempt went through; safe to continue processing
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
    client.acknowledgeJob(r -> r.jobId(jobId).nonce(nonce));
} catch (InvalidJobStateException e) {
    if (e.getMessage().contains("already been acknowledged")) {
        logger.info("job {} acknowledged by earlier attempt", jobId); // treat as success
    } else throw e;
}

Prevention

When it happens

Trigger: Calling AcknowledgeJob twice for the same jobId; a worker retry after a network timeout where the first acknowledge actually succeeded; two worker instances picking up the same job (nonce shared) and both acknowledging.

Common situations: At-least-once retry logic in the worker that does not remember prior acknowledgments; duplicated messages in the worker's internal queue; SDK client retries enabled at the HTTP layer causing invisible double submit.

Related errors


AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14). Data as JSON: /api/errors/2746cb29c27c4aab. Report an issue: GitHub.