Bug 14643 - possible issue with System.Timer and threads
Summary: possible issue with System.Timer and threads
Status: RESOLVED NORESPONSE
Alias: None
Product: Android
Classification: Xamarin
Component: BCL Class Libraries ()
Version: 4.8.x
Hardware: Macintosh Mac OS
: --- normal
Target Milestone: ---
Assignee: Jonathan Pryor
URL:
Depends on:
Blocks:
 
Reported: 2013-09-11 09:51 UTC by David Schulte
Modified: 2017-06-29 23:14 UTC (History)
3 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 Developer Community or GitHub 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 NORESPONSE

Description David Schulte 2013-09-11 09:51:26 UTC
I have found that Xamarin's System.Timer implementation may be keeping a reference to the thread from which any Elapsed event handler is registered and the Start() method invoked.

Our application instantiates a timer via a background thread that also includes an idle handler. The purpose of the timer is to act as a backup to the idle handler, whose main purpose is to quit the given background thread. Should the background thread still exist at the time the timer event fires, the timer event then quits the background thread. We are using a single method that contains the use of a semaphore to ensure that should both the idle handler and timer events fire at the same time, only one will win, and we also maintain a boolean that indicates whether the background thread has been terminated. The maintenance of this boolean is within the critical region maintained by the semaphore.

What I found is that if I invoke the following to calls on from the background thread and the idle handler fires, I end up seeing the error below and our application locks/crashes:

_timerForTimingOutQuitWhenIdleState.Elapsed += OnTimer;
_timerForTimingOutQuitWhenIdleState.Start ();


[MessageQueue] Handler (android.view.ViewRootImpl) {a0756fb0} sending message to a Handler on a dead thread
[MessageQueue] java.lang.RuntimeException: Handler (android.view.ViewRootImpl) {a0756fb0} sending message to a Handler on a dead thread
[MessageQueue]   at android.os.MessageQueue.enqueueMessage(MessageQueue.java:196)
[MessageQueue]   at android.os.Handler.sendMessageAtTime(Handler.java:473)
[MessageQueue]   at android.os.Handler.sendMessageDelayed(Handler.java:446)
[MessageQueue]   at android.os.Handler.sendMessage(Handler.java:383)
[MessageQueue]   at android.view.ViewRootImpl.dispatchAppVisibility(ViewRootImpl.java:3927)
[MessageQueue]   at android.view.ViewRootImpl$W.dispatchAppVisibility(ViewRootImpl.java:4131)
[MessageQueue]   at android.view.IWindow$Stub.onTransact(IWindow.java:106)
[MessageQueue]   at android.os.Binder.execTransact(Binder.java:338)
[MessageQueue]   at dalvik.system.NativeStart.run(Native Method)


If I eliminate the idle handler the problem goes away, and if I run the 2 lines of code above on the main UI thread (using RunOnUiThread()) the problem also goes away, even with the idle handler in place. This implies that even though timers are supposed to exist on their own thread it appears the current System.Timer implementation may be trying to invoke the Elapsed handler on the thread from which the timer was started, which in our case may no longer exist at the time the timer elapses.

I should also note that the common method invoked by the idle handler and Elapsed handler does invoke Stop() on the timer, however we understand that doing so is not a guarantee that the Elapsed event still won't fire given that the timer is running on its own thread (both the droid and System.Timer documentation warn about this). We handle this via a semaphore and the boolean described above.

Could someone please look at Xamarin's System.Timer implementation to see if the condition described above is a possible bug? We have worked around this problem by simply running the Elapsed and Start() statements on the UI thread which we know will not go away.
Thanks in advance,
Dave Schulte
Comment 1 David Schulte 2013-09-11 20:48:41 UTC
The solution that we opted for was to eliminate the use of the idle handler to quit the worker/looper thread and rely just on the timer event. Again, the problem noted earlier would only occur if the worker/looper thread were terminated before the timer event were to fire, and the error message noted earlier would appear at about the time the timer event would have fired.
Thanks again,
Dave
Comment 2 Jonathan Pryor 2013-09-20 14:46:58 UTC
Please provide a complete repro case. I'm not sure I fully understand your scenario.

My interpretation of your description is:

  using System;
  using System.Threading;
  using System.Timers;

  using Android.App;
  using Android.Widget;
  using Android.OS;

  using Timer = System.Timers.Timer;

  namespace Scratch.TimerElapsed
  {
    [Activity (Label = "Scratch.TimerElapsed", MainLauncher = true)]
      public class MainActivity : Activity
    {
      int count = 1;
      Timer timer;

      protected override void OnCreate (Bundle bundle)
      {
        base.OnCreate (bundle);

        Thread t = null;
        t = new Thread (() => {
            Console.WriteLine ("# Creating Timer on thread: {0} Id={1}",
                t.Name, t.ManagedThreadId);
            timer = new Timer (1000);
            timer.Elapsed += (object sender, ElapsedEventArgs e) => {
                var _t = Thread.CurrentThread;
                Console.WriteLine ("# Elapsed! count={0}; thread: {1} Id={2}",
                    count, _t.Name, _t.ManagedThreadId);
            };
            timer.Start ();
        });
        t.Name = "jonp thread!";
        t.Start ();
        t.Join ();
        Console.WriteLine ("Thread state: {0}", t.ThreadState);


        // Set our view from the "main" layout resource
        SetContentView (Resource.Layout.Main);

        // Get our button from the layout resource,
        // and attach an event to it
        Button button = FindViewById<Button> (Resource.Id.myButton);

        button.Click += delegate {
          button.Text = string.Format ("{0} clicks!", count++);
        };
      }
    }
  }

A new thread ("jonp thread!") is created which creates and starts the System.Timers.Timer instance.

Logging produces:

> I/mono-stdout(19341): # Creating Timer on thread: jonp thread! Id=3
> I/mono-stdout(19341): Thread state: Stopped
> I/mono-stdout(19341): # Elapsed! count=1; thread: Threadpool worker Id=6
...

As per the log, the Timer.Elapsed callback is being invoked on a different thread than the one that created the Timer instance, so it doesn't appear to be "trying to invoke the Elapsed handler on the thread from which the timer was started"
Comment 3 David Schulte 2013-09-24 14:32:34 UTC
Jonathan,
Unfortunately, I haven't been given the time to try to come up with a smaller example for you. You are on the right track with what you tried. We are actually creating a HandlerThread, which spawns the Timer thread. We then quit the HandlerThread before the Timer goes off. In this scenario, either the droid framework or Xamarin appears to try to send a message back to the Handler thread, which produces the error that I noted earlier. We eliminated the Quit() call that could have potentially occurred in advance of the timer event and the problem went away. When this problem does occur, we never make it into the Timer handler/delegate. The error occurs before this.
If I ever get a chance, I will try to come up with a smaller example that I can post for you.
Dave
Comment 4 Kent Green [MSFT] 2017-06-29 23:14:54 UTC
Because we have not received a reply to our request for more information (a complete repo per comment #2) we are closing this issue. If you are still encountering this issue, please reopen the ticket with the requested information. Thanks!