FubarDevelopment / FtpServer

Portable FTP server written in .NET
http://fubardevelopment.github.io/FtpServer/
MIT License
482 stars 163 forks source link

The semaphore has been disposed. #102

Open alexandrius007 opened 4 years ago

alexandrius007 commented 4 years ago

I get error

The semaphore has been disposed.

Could you help to understend the reason of the error? After error Program has critical error.

<Exception>
<ExceptionType>System.ObjectDisposedException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>
<Message>The semaphore has been disposed.</Message>
<StackTrace>
at System.Threading.SemaphoreSlim.CheckDispose()
at System.Threading.SemaphoreSlim.Release(Int32 releaseCount)
at FubarDev.FtpServer.Networking.PausableFtpService.&lt;&gt;c__DisplayClass18_0.&lt;StartAsync&gt;b__0(FtpServiceStatus status)
at System.Progress`1.InvokeHandlers(Object state)
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
</StackTrace>
<ExceptionString>System.ObjectDisposedException: The semaphore has been disposed.
   at System.Threading.SemaphoreSlim.CheckDispose()
   at System.Threading.SemaphoreSlim.Release(Int32 releaseCount)
   at FubarDev.FtpServer.Networking.PausableFtpService.&lt;&gt;c__DisplayClass18_0.&lt;StartAsync&gt;b__0(FtpServiceStatus status)
   at System.Progress`1.InvokeHandlers(Object state)
   at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()</ExceptionString>
</Exception>
dmansurov83 commented 3 years ago

We have the same case when the connection was closed immediately after opening. I guess the Progress implementation is to blame here:

using (var semaphore = new SemaphoreSlim(0, 1))
{
    _jobPaused = new CancellationTokenSource();
    _task = RunAsync(
        new Progress<FtpServiceStatus>(
            status =>
            {
                Status = status;

                if (status == FtpServiceStatus.Running)
                {
                    // ReSharper disable once AccessToDisposedClosure
                    semaphore.Release();
                }
            }));

    await semaphore.WaitAsync(cancellationToken);
}

The Progress uses SyncronizationContext.Post to pass progress data and, theoretically, can be invoked after the semaphore will be disposed.

heppth commented 3 years ago

I have encountered the same issue. In my case, the problem occurred in conjunction with SslStreamConnectionAdapter (explicit encryption).

This exception encountered before: Status must be Running, Stopped, or Paused, but was ReadyToRun.

System.InvalidOperationException:
   at FubarDev.FtpServer.Networking.PausableFtpService+<StopAsync>d__19.MoveNext (FubarDev.FtpServer, Version=3.1.1.0, Culture=neutral, PublicKeyToken=null)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at FubarDev.FtpServer.ConnectionHandlers.SslStreamConnectionAdapter+<StopAsync>d__12.MoveNext (FubarDev.FtpServer, Version=3.1.1.0, Culture=neutral, PublicKeyToken=null)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at FubarDev.FtpServer.FtpConnection+<StopAsync>d__61.MoveNext (FubarDev.FtpServer, Version=3.1.1.0, Culture=neutral, PublicKeyToken=null)

Immediately after that the same exception occured: The semaphore has been disposed.

System.ObjectDisposedException:
   at System.Threading.SemaphoreSlim.CheckDispose (System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Threading.SemaphoreSlim.Release (System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at FubarDev.FtpServer.Networking.PausableFtpService+<>c__DisplayClass18_0.<StartAsync>b__0 (FubarDev.FtpServer, Version=3.1.1.0, Culture=neutral, PublicKeyToken=null)
   at System.Progress`1.InvokeHandlers (System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Threading.ThreadPoolWorkQueue.Dispatch (System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
alexandrius007 commented 3 years ago

This class extension helps to solve this problem:

 internal class SemaphoreSlimExt : SemaphoreSlim
    {
             public SemaphoreSlimExt(int initialCount)
            : base(initialCount)
        {
        }
        public SemaphoreSlimExt(int initialCount, int maxCount)
            : base(initialCount, maxCount)
        {
        }

        public bool IsDisposed { get; internal set; }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            IsDisposed = true;
        }
    }

and after

  using (var semaphore = new SemaphoreSlimExt(0, 1))
                {
                    _jobPaused = new CancellationTokenSource();
                    _task = RunAsync(
                        new Progress<FtpServiceStatus>(
                            status =>
                            {
                                Status = status;

                                if (status == FtpServiceStatus.Running && **!semaphore.IsDisposed**)
                                {
                                    // ReSharper disable once AccessToDisposedClosure
                                    semaphore?.Release();
                                }
                            }));

                    var res = semaphore.WaitAsync(cancellationToken);
                    res.Wait();
                }