williamb1024/fs-processes

ObjectDisposedException

discostu105 opened this issue · 2 comments

Hello. Thanks for this awesome library. It's very useful!

I've just integrated it into my app and it works most of the time, but sometimes I get this exception:

System.ObjectDisposedException: Safe handle has been closed.
Object name: 'SafeHandle'.
   at System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean& success)
   at Fs.Processes.JobObjects.JobObject.Notification(JobObjectMessage notifyMessage, IntPtr notifyData)
   at Fs.Processes.JobObjects.JobObjectCompletionPort.IoCompletionPort.IoCompletionPortReader()
--- End of stack trace from previous location ---
   at Fs.Processes.JobObjects.JobObjectCompletionPort.IoCompletionPort.<>c.<IoCompletionPortReader>b__6_0(Object edi)
   at System.Threading.ThreadPoolWorkQueue.Dispatch()

Do you have an idea what's wrong? Shouldn't I dispose the JobObject?

Here is my code:

private async Task<ProcessRunner> Start() {
	Console.WriteLine($"starting process. {this.ToString()}");
	await Task.Run(async () => {
		try {
			using var jobObject = new JobObject();
			var startInfo = new Fs.Processes.CreateProcessInfo() {
				FileName = executable,
				WorkingDirectory = workingDir.FullName,
				Arguments = string.Join(" ", arguments),
				RedirectStandardOutput = redirectOutput,
				RedirectStandardError = redirectError,
				WindowShow = Fs.Processes.WindowShow.Hidden
			};

			using var process = StartProcess(jobObject, startInfo);

			Task outTask = null;
			Task errTask = null;
			if (redirectOutput) {
				process.OutputDataReceived += (s, d) => { if (d.Data != null) stdOutSb.AppendLine(d.Data); };
				outTask = process.BeginReadingStandardOutputAsync();
			}
			if (redirectError) {
				process.ErrorDataReceived += (s, d) => { if (d.Data != null) stdErrSb.AppendLine(d.Data); };
				errTask = process.BeginReadingStandardErrorAsync();
			}
			if (outTask != null) await outTask;
			if (errTask != null) await errTask;

			await process.Exited;
			ExitCode = process.ExitCode ?? -1;
			
		} catch (Exception e) {
			throw new ProcessRunnerException($"An exception occurred while starting a process: {this.ToString()}", e);
		}
	});
	return this;
}

private Fs.Processes.Process StartProcess(JobObject jobObjects, Fs.Processes.CreateProcessInfo startInfo) {
	if (maxMemoryMb.HasValue || maxCpuPercent.HasValue) {
		var jobLimits = new JobLimits();
		if (maxMemoryMb.HasValue) {
			jobLimits.MaximumProcessMemory = maxMemoryMb.Value * 1024 * 1024;
		}
		if (maxCpuPercent.HasValue) {
			jobLimits.CpuRate = new CpuRateLimit(maxCpuPercent.Value, true);
		}
		jobObjects.SetLimits(jobLimits);
		return jobObjects.CreateProcess(startInfo);
	} else {
		return new Fs.Processes.Process(startInfo);
	}
}

I'd be grateful for any hints :).

I believe the error that you are experiencing is a race condition between a kernel notification and the JobObject SafeHandle being disposed.

Until I (or someone) can add some protection into the routines to ensure that the race doesn't occur, you can add:

await jobObject.Idle;

after you assign the ExitCode value.

Awesome, thank you for responding! I will try that workaround.