Bug 36143 - Thread Abort method sometimes doesn't work
Summary: Thread Abort method sometimes doesn't work
Status: RESOLVED ANSWERED
Alias: None
Product: Runtime
Classification: Mono
Component: General ()
Version: 4.2.0 (C6)
Hardware: PC Linux
: --- normal
Target Milestone: ---
Assignee: Ludovic Henry
URL:
Depends on:
Blocks:
 
Reported: 2015-11-23 04:49 UTC by BogdanovKirill
Modified: 2016-01-25 15:40 UTC (History)
5 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on GitHub or Developer Community with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED ANSWERED

Description BogdanovKirill 2015-11-23 04:49:06 UTC
Just try to run that simple program and you will see that after some time it will hung at "workThread.Join".

		static void WorkFunc()
		{
			bool l = true;

			while (l) {
			}
		}

		static void Main(string[] args)
		{
			int i = 0;
			while(true)
			{
				var workThread = new Thread (WorkFunc) {
					IsBackground = true,
				};
				workThread.Start ();
				workThread.Abort ();

				workThread.Join ();
				Console.WriteLine (i++);
			}
		}

Tested on Ubuntu 14.04.3 LTS with mono 4.2.1
Comment 1 Rodrigo Kumpera 2015-11-27 17:40:28 UTC
This program is inherently racy as Start doesn't wait the other thread reach a runnable state that is abortable.

This means that Abort can be called at a point where the thread can't be aborted and the request will be discarded.

In general, avoid using Thread.Abort as it does, by design, cause memory corruption.
Comment 2 BogdanovKirill 2015-11-28 09:44:50 UTC
Thank you for your answer Rodrigo. I agree that my example is not correct. So what's about patched version. It also could hang after several time. Check this please:

			static void WorkFunc(object ss)
			{
				bool l = true;
				((AutoResetEvent)ss).Set();
				while (l) {
					
				}
			}

			static void Main(string[] args)
			{
				int i = 0;
				while(true)
				{
					var workThread = new Thread (new ParameterizedThreadStart(WorkFunc)) {
						IsBackground = true,
					};

					var @event = new AutoResetEvent (false);
					workThread.Start (@event);
					@event.WaitOne ();
					
					workThread.Abort ();

					workThread.Join ();
					Console.WriteLine (i++);
				}
			}
Comment 3 Ludovic Henry 2015-12-10 15:11:48 UTC
I could reproduce on ubuntu 64bits, I will work on it ASAP.

Thank you for the report!
Comment 4 Ludovic Henry 2015-12-10 15:14:19 UTC
Also, I couldn't reproduce on master (I didn't hit the crash after more than 2.5 million thread started/aborted/joined), so it must have been fixed since then.
Comment 5 BogdanovKirill 2015-12-13 10:44:35 UTC
Thank you for your feedback! Whether any changes will made to stable version?
Comment 6 Ludovic Henry 2015-12-13 12:00:22 UTC
We will try to backport it, but it won't happen for 4.2.1. Hopefully, it will make it into 4.2.2.
Comment 7 Ludovic Henry 2015-12-14 16:45:02 UTC
I finally found the issue, and as I initially thought, this is a very unlucky occurence, but fortunately, there is a very easy fix.

The way we do Thread.Abort is the following: if the thread that we want to abort is currently in native code (in a internal call, a p/invoke, a wrapper, etc.), we simply set a flag on the thread. Then when this thread comes out of managed code, it checks this flag, and if it is set, it throws the ThreadAbortException.

But the problem is: the function that check this flag is in native code (https://github.com/mono/mono/blob/mono-4.2.0-branch/mono/metadata/threads.c#L4195). And when going out of the internal call to this native function, we do not check the flag again (otherwise we would have an infinite loop).
If you are very unlucky, you are going to install the flag on the thread, while exiting this function. This means you already checked the flag (which wasn't set), but you are not going to check this flag again.

A simple fix is to guarantee that you have periodical internal calls in your while loop in WorkFunc. You can achieve this by calling Thread.Yield (), for example with the following:

static void WorkFunc (object ss)
{
	((AutoResetEvent) ss).Set ();

	uint j = 1;
	while (true) {
		if ((j = (j + 1) % 1000) == 0)
			Thread.Yield ();
	}
}

Another workaround, is to have the following where you want to abort and join:

static void Main (object ss)
{
	[...]

	do {
		thread.Abort ();
	} while (!thread.Join (1000));

	[...]
}

I can run for multiple millions thread start/abort/join cycles without issue with the first workaround.
Comment 8 BogdanovKirill 2015-12-15 03:43:15 UTC
Thank you for your answer, Ludovic. Your solution is clear. But could you explain why it can work on Windows, but can't work on Linux ? Is there any special issue in Linux OS that doesn't allow us to make it work without workarounds?