Bug 17224 - InvalidOperationException when using async stream operations
Summary: InvalidOperationException when using async stream operations
Status: NEEDINFO
Alias: None
Product: Class Libraries
Classification: Mono
Component: System (show other bugs)
Version: 3.2.x
Hardware: PC Windows
: --- normal
Target Milestone: Untriaged
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2014-01-14 06:17 UTC by Øystein Krog
Modified: 2014-01-27 05:13 UTC (History)
2 users (show)

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


Attachments

Description Øystein Krog 2014-01-14 06:17:39 UTC
When our app is under heavy load it tends to crash with this exception:

Jan 14 11:47:27 Initial-Force-Ipad-Mini ScAppiOS[7082] <Notice>: [ERROR] Application - Main(): Critical application error
	System.InvalidOperationException: The task has already completed
	  at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException (System.Exception exception) [0x00000] in <filename unknown>:0 
	  at InitialForce.Utils.Extensions.StreamExtensions+<CopyToAsync>c__async5.MoveNext () [0x00000] in <filename unknown>:0 
	  at System.Threading.Tasks.SynchronizationContextContinuation.<Execute>m__0 (System.Object l) [0x00000] in <filename unknown>:0 
	  at MonoTouch.UIKit.UIKitSynchronizationContext+<Post>c__AnonStorey89.<>m__A8 () [0x00000] in <filename unknown>:0 
	  at MonoTouch.Foundation.NSAsyncActionDispatcher.Apply () [0x00000] in <filename unknown>:0 
	  at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr)
	  at MonoTouch.UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x00000] in <filename unknown>:0 
	  at ScApp.iOS.Application.Main (System.String[] args) [0x00000] in <filename unknown>:0 
Jan 14 11:47:32 Initial-Force-Ipad-Mini cplogd[7078] <Warning>: Exiting.

StreamExtension is fairly simple:
public static class StreamExtensions
    {
        public static async Task CopyToAsync(this Stream source,
            Stream destination,
            IProgress<long> progress,
            CancellationToken cancellationToken = default(CancellationToken),
            int bufferSize = 0x1000)
        {
            var buffer = new byte[bufferSize];
            int bytesRead;
            long totalRead = 0;
            while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
            {
                await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken);
                cancellationToken.ThrowIfCancellationRequested();
                totalRead += bytesRead;
                // progress support
                progress.Report(totalRead);
            }
        }
    }

I suspect this is a bug in the Tasks code.

=== Xamarin Studio ===

Version 4.3.1 (build 3)
Installation UUID: 1d6c823d-a024-4e68-999f-4570d68869f8
Runtime:
	Mono 3.2.5 ((no/964e8f0)
	GTK+ 2.24.20 theme: Raleigh
	GTK# (2.12.0.0)
	Package version: 302050000

=== Apple Developer Tools ===

Xcode 5.0.2 (3335.32)
Build 5A3005

=== Xamarin.Mac ===

Xamarin.Mac: Not Installed

=== Xamarin.Android ===

Version: 4.11.0 (Business Edition)
Android SDK: /Users/oysteinkrog/Library/Developer/Xamarin/android-sdk-mac_x86
	Supported Android versions:
		2.1   (API level 7)
		2.2   (API level 8)
		2.3   (API level 10)
		3.1   (API level 12)
		4.0   (API level 14)
		4.0.3 (API level 15)
Java SDK: /usr
java version "1.6.0_65"
Java(TM) SE Runtime Environment (build 1.6.0_65-b14-462-11M4609)
Java HotSpot(TM) 64-Bit Server VM (build 20.65-b04-462, mixed mode)

=== Xamarin.iOS ===

Version: 7.0.6.166 (Business Edition)
Hash: eac7b15
Branch: 
Build date: 2013-16-12 16:06:53-0500

=== Build Information ===

Release ID: 403010003
Git revision: bd21181650b29ffdb2c231beccb76097f3e7a399
Build date: 2014-01-09 00:12:45+0000
Xamarin addins: d56f838106065386208d53d6f1b34240068a919e

=== Operating System ===

Mac OS X 10.8.5
Darwin Oysteins-Mac.local 12.5.0 Darwin Kernel Version 12.5.0
    Sun Sep 29 13:33:47 PDT 2013
    root:xnu-2050.48.12~1/RELEASE_X86_64 x86_64
Comment 1 Marek Safar 2014-01-14 13:26:54 UTC
Any chance to get some sort of repro?
Comment 2 Øystein Krog 2014-01-15 09:48:40 UTC
I'll try to create repro Marek, but it may take me a little while.
Comment 3 Øystein Krog 2014-01-22 08:04:06 UTC
I've been able to find a workaround for this issue.
We use an ExplicitThreadScheduler (with a single thread) to run all queries against sqlite on a separate thread (for other/legacy reasons).
When I changed the tasks created on this scheduler (in sqlite.net async connection) to pass TaskCreationOptions.DenyChildAttach, it seems to have fixed the problem.
Unfortunately I'm not sure if I can spare the time to create a repro-case.

    /// <summary>Provides a scheduler that uses an explicit number of threads for scheduling tasks.</summary>
    public sealed class ExplicitThreadsTaskSchedulerIOS : TaskScheduler, IDisposable
    {
        /// <summary>The threads used by the scheduler.</summary>
        private readonly List<Thread> _threads;

        /// <summary>Stores the queued tasks to be executed by our threads.</summary>
        private BlockingCollection<Task> _tasks;

        /// <summary>Initializes a new instance of the class with the specified concurrency level.</summary>
        /// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param>
        /// <param name="threadName">The name for the thread(s), useful for debugging etc.</param>
        public ExplicitThreadsTaskSchedulerIOS(int numberOfThreads, string threadName = null)
        {
            // Validate arguments
            if (numberOfThreads < 1)
            {
                throw new ArgumentOutOfRangeException("numberOfThreads");
            }

            threadName = threadName ?? "ExplicitThreadsTaskSchedulerIOS";

            // Initialize the tasks collection
            _tasks = new BlockingCollection<Task>();

            // Create the threads to be used by this scheduler
            _threads = Enumerable.Range(0, numberOfThreads)
                .Select(i =>
                {
                    var thread = new Thread(() =>
                    {
                        // Continually get the next task and try to execute it.
                        // This will continue until the scheduler is disposed and no more tasks remain.
                        foreach (Task t in _tasks.GetConsumingEnumerable())
                        {
                            TryExecuteTask(t);
                        }
                    });
                    thread.IsBackground = true;
                    thread.Name = string.Format("{0} {1}", threadName, i);
                    return thread;
                })
                .ToList();

            // Start all of the threads
            _threads.ForEach(t => t.Start());
        }

        /// <summary>
        ///     Cleans up the scheduler by indicating that no more tasks will be queued.
        ///     This method blocks until all threads successfully shutdown.
        /// </summary>
        public void Dispose()
        {
            if (_tasks != null)
            {
                // Indicate that no new tasks will be coming in
                _tasks.CompleteAdding();

                // Wait for all threads to finish processing tasks
                foreach (Thread thread in _threads)
                {
                    thread.Join();
                }

                // Cleanup
                _tasks.Dispose();
                _tasks = null;
            }
        }

        /// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
        public override int MaximumConcurrencyLevel
        {
            get { return _threads.Count; }
        }

        /// <summary>Provides a list of the scheduled tasks for the debugger to consume.</summary>
        /// <returns>An enumerable of all tasks currently scheduled.</returns>
        protected override IEnumerable<Task> GetScheduledTasks()
        {
            // Serialize the contents of the blocking collection of tasks for the debugger
            return _tasks.ToArray();
        }

        /// <summary>Queues a Task to be executed by this scheduler.</summary>
        /// <param name="task">The task to be executed.</param>
        protected override void QueueTask(Task task)
        {
            // Push it into the blocking collection of tasks
            _tasks.Add(task);
        }

        /// <summary>Determines whether a Task may be inlined.</summary>
        /// <param name="task">The task to be executed.</param>
        /// <param name="taskWasPreviouslyQueued">Whether the task was previously queued.</param>
        /// <returns>true if the task was successfully inlined; otherwise, false.</returns>
        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
        {
            // Try to inline if the current thread is one of our internal threads
            return _threads.Contains(Thread.CurrentThread) && TryExecuteTask(task);
        }
    }
Comment 4 Marek Safar 2014-01-27 05:13:20 UTC
Mono master has fixes which are not part of 7.0.6.166 and may well fix the issue. You'll probably have to wait for 7.0.7 and try again if we don't have way to reproduce the issue and verify it's resolved

Note You need to log in before you can comment on or make changes to this bug.