diff --git a/src/Microsoft.Azure.WebJobs.Host/Singleton/SingletonListener.cs b/src/Microsoft.Azure.WebJobs.Host/Singleton/SingletonListener.cs index c5c017fdd..f8df04e8f 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Singleton/SingletonListener.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Singleton/SingletonListener.cs @@ -67,7 +67,15 @@ public async Task StartAsync(CancellationToken cancellationToken) return; } - await _innerListener.StartAsync(cancellationToken); + try + { + await _innerListener.StartAsync(cancellationToken); + } + catch + { + await ReleaseLockAsync(cancellationToken); + throw; + } _isListening = true; } @@ -132,7 +140,15 @@ internal async Task TryAcquireLock() LockTimer = null; } - await _innerListener.StartAsync(CancellationToken.None); + try + { + await _innerListener.StartAsync(CancellationToken.None); + } + catch + { + await ReleaseLockAsync(CancellationToken.None); + throw; + } _isListening = true; } diff --git a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Singleton/SingletonListenerTests.cs b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Singleton/SingletonListenerTests.cs index 6687d78a5..85df8bc76 100644 --- a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Singleton/SingletonListenerTests.cs +++ b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Singleton/SingletonListenerTests.cs @@ -100,6 +100,44 @@ public async Task StartAsync_DoesNotStartLockTimer_WhenPollingIntervalSetToInfin _mockSingletonManager.VerifyAll(); } + [Fact] + public async Task StartAsync_InnerListenerThrows_ReleasesLock() + { + CancellationToken cancellationToken = new CancellationToken(); + var lockHandle = new RenewableLockHandle(new SingletonLockHandle(), null); + _mockSingletonManager.Setup(p => p.TryLockAsync(_lockId, null, _attribute, cancellationToken, false)) + .ReturnsAsync(lockHandle); + _mockInnerListener.Setup(p => p.StartAsync(cancellationToken)).ThrowsAsync(new InvalidOperationException("Listener failed")); + _mockSingletonManager.Setup(p => p.ReleaseLockAsync(lockHandle, cancellationToken)).Returns(Task.FromResult(true)); + + await Assert.ThrowsAsync(() => _listener.StartAsync(cancellationToken)); + + _mockSingletonManager.VerifyAll(); + _mockInnerListener.VerifyAll(); + } + + [Fact] + public async Task TryAcquireLock_InnerListenerThrows_ReleasesLock() + { + _listener.LockTimer = new System.Timers.Timer + { + Interval = 30 * 1000 + }; + _listener.LockTimer.Start(); + + RenewableLockHandle lockHandle = new RenewableLockHandle(new SingletonLockHandle(), null); + _mockSingletonManager.Setup(p => p.TryLockAsync(_lockId, null, _attribute, CancellationToken.None, false)) + .ReturnsAsync(lockHandle); + _mockInnerListener.Setup(p => p.StartAsync(CancellationToken.None)).ThrowsAsync(new InvalidOperationException("Listener failed")); + _mockSingletonManager.Setup(p => p.ReleaseLockAsync(lockHandle, CancellationToken.None)).Returns(Task.FromResult(true)); + + await Assert.ThrowsAsync(() => _listener.TryAcquireLock()); + + Assert.Null(_listener.LockTimer); + _mockSingletonManager.VerifyAll(); + _mockInnerListener.VerifyAll(); + } + [Fact] public async Task TryAcquireLock_WhenLockAcquired_StopsLockTimerAndStartsListener() {