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
- 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).
- Separate transactional work from disposal: commit/persist the job's result before Dispose runs, so a disposal exception cannot imply a lost job.
- 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
- Make IDisposable.Dispose on job classes idempotent and exception-safe.
- Move disposable resource lifetimes into the job method (using) rather than the class.
- Persist results before Dispose runs.
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
- An exception occurred during job activation.
- An exception occurred during arguments deserialization.
- activator
- JobActivator returned NULL instance of the '{Type}' type.
- Could not set parameter for a created job.
AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13).
Data as JSON: /api/errors/cf7bad319afc4d71.
Report an issue: GitHub.