HangfireIO/Hangfire · error · JobPerformanceException

Job has been performed, but an exception occurred during dis

Error message

Job has been performed, but an exception occurred during disposal.

What it means

Thrown as JobPerformanceException (message 'Job has been performed, but an exception occurred during disposal.') wrapping any catchable exception raised while disposing the job instance after successful invocation. The obsolete Dispose() static method (lines 148-162) casts the instance to IDisposable and calls Dispose() inside a try/catch, so a failing Dispose still surfaces as a performance error even though the job logic itself completed.

Source

Thrown at src/Hangfire.Core/Obsolete/Job.Obsolete.cs:158

            }
            catch (TargetInvocationException ex)
            {
                CoreBackgroundJobPerformer.HandleJobPerformanceException(ex.InnerException, cancellationToken, null);
                throw;
            }
        }

        [Obsolete("Will be removed in 2.0.0")]
        private static void Dispose(object instance)
        {
            try
            {
                var disposable = instance as IDisposable;
                disposable?.Dispose();
            }
            catch (Exception ex) when (ex.IsCatchableExceptionType())
            {
                throw new JobPerformanceException(
                    "Job has been performed, but an exception occurred during disposal.",
                    ex);
            }
        }
    }
}

View on GitHub (pinned to c236dd0f93)

Solutions

  1. Inspect the InnerException to find the disposal failure and make the job's Dispose() idempotent and exception-safe (guard against double-dispose, swallow non-catal cleanup errors).
  2. Separate transactional work from disposal: commit/persist the job's result before Dispose runs, so a disposal exception cannot imply a lost job.
  3. Consider moving disposable resource lifetimes into the job method body with 'using' rather than implementing IDisposable on the job class itself.

Example fix

// before — Dispose throws on already-closed connection
public void Dispose() { _connection.Close(); }

// after — idempotent, exception-safe disposal
private bool _disposed;
public void Dispose()
{
    if (_disposed) return;
    try { _connection.Close(); } finally { _disposed = true; }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Make the job's Dispose idempotent before performing so cleanup can't throw:
// (see exampleFix) — there is no pre-check that predicts a disposal failure.

Try / catch

try { job.Perform(activator, token); }
catch (JobPerformanceException ex) when (ex.Message.Contains("during disposal"))
{
    logger.LogWarning(ex.InnerException, "Job succeeded but disposal failed.");
}

Prevention

When it happens

Trigger: Job.Perform (obsolete) finishes invoking the method, then in its 'finally' block calls Dispose(instance). The instance implements IDisposable and its Dispose() throws — e.g. flushing a buffer to a closed connection, closing an already-disposed resource, or releasing a native handle that has been invalidated.

Common situations: The job class's Dispose() closes a database connection or file handle that was already disposed by the job method; Dispose() flushes to a network stream whose underlying socket has been torn down; a third-party dependency's Dispose throws on shutdown. Note the job result was produced successfully — only cleanup failed.

Related errors


AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13). Data as JSON: /api/errors/cf7bad319afc4d71. Report an issue: GitHub.