Invoking a command waits up to 30s for NuGet or another process (#4657)

* Invoking a command waits up to 30s for NuGet or another process

* PR Feedback
This commit is contained in:
Piotr Puszkiewicz 2016-11-08 23:23:13 -08:00 committed by GitHub
parent a700604b93
commit b918b2a6b6
10 changed files with 316 additions and 4 deletions

View file

@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.DotNet.Tools.Test.Utilities
{
public static partial class FileInfoExtensions
{
private class FileInfoLock : IDisposable
{
private FileStream _fileStream;
public FileInfoLock(FileInfo fileInfo)
{
_fileStream = fileInfo.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
public void Dispose()
{
_fileStream.Dispose();
}
}
}
}

View file

@ -0,0 +1,57 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NuGet.Common;
namespace Microsoft.DotNet.Tools.Test.Utilities
{
public static partial class FileInfoExtensions
{
private class FileInfoNuGetLock : IDisposable
{
private FileStream _fileStream;
private CancellationTokenSource _cancellationTokenSource;
private Task _task;
public FileInfoNuGetLock(FileInfo fileInfo)
{
_cancellationTokenSource = new CancellationTokenSource();
_task = ConcurrencyUtilities.ExecuteWithFileLockedAsync<int>(
fileInfo.FullName,
lockedToken =>
{
Task.Delay(60000, _cancellationTokenSource.Token).Wait();
return Task.FromResult(0);
},
_cancellationTokenSource.Token);
}
public void Dispose()
{
_cancellationTokenSource.Cancel();
try
{
_task.Wait();
}
catch (AggregateException)
{
}
finally
{
_cancellationTokenSource.Dispose();
}
}
}
}
}

View file

@ -9,16 +9,21 @@ using System.Threading.Tasks;
namespace Microsoft.DotNet.Tools.Test.Utilities
{
public static class FileInfoExtensions
public static partial class FileInfoExtensions
{
public static FileInfoAssertions Should(this FileInfo file)
{
return new FileInfoAssertions(file);
}
public static FileInfo Sub(this FileInfo file, string name)
public static IDisposable Lock(this FileInfo subject)
{
return new FileInfo(Path.Combine(file.FullName, name));
return new FileInfoLock(subject);
}
public static IDisposable NuGetLock(this FileInfo subject)
{
return new FileInfoNuGetLock(subject);
}
}
}

View file

@ -0,0 +1,83 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.DotNet.Tools.Test.Utilities
{
public static class IDisposableExtensions
{
public static IDisposable DisposeAfter(this IDisposable subject, TimeSpan timeout)
{
return new IDisposableWithTimeout(subject, timeout);
}
private class IDisposableWithTimeout :IDisposable
{
private CancellationTokenSource _cancellationTokenSource;
private Task _timeoutTask;
private bool _isDisposed;
private IDisposable _subject;
public IDisposableWithTimeout(IDisposable subject, TimeSpan timeout)
{
_subject = subject;
_cancellationTokenSource = new CancellationTokenSource();
_timeoutTask = Task.Run(() =>
{
Task.Delay(timeout, _cancellationTokenSource.Token).Wait();
DisposeInternal();
},
_cancellationTokenSource.Token);
}
public void Dispose()
{
DisposeInternal();
CancelTimeout();
}
private void DisposeInternal()
{
lock(this)
{
if (!_isDisposed)
{
_subject.Dispose();
_isDisposed = true;
}
}
}
private void CancelTimeout()
{
_cancellationTokenSource.Cancel();
try
{
_timeoutTask.Wait();
}
catch (AggregateException)
{
}
finally
{
_cancellationTokenSource.Dispose();
}
}
}
}
}

View file

@ -221,6 +221,50 @@ namespace Microsoft.DotNet.Tests
result.Should().Fail();
}
[Fact]
public void When_assets_file_is_in_use_Then_CLI_retries_launching_the_command_for_at_least_one_second()
{
var testInstance = TestAssets.Get("AppWithToolDependency")
.CreateInstance()
.WithSourceFiles()
.WithRestoreFiles();
var assetsFile = testInstance.Root.GetDirectory("obj").GetFile("project.assets.json");
using (assetsFile.Lock()
.DisposeAfter(TimeSpan.FromMilliseconds(1000)))
{
new PortableCommand()
.WithWorkingDirectory(testInstance.Root)
.ExecuteWithCapturedOutput()
.Should().HaveStdOutContaining("Hello Portable World!" + Environment.NewLine)
.And.NotHaveStdErr()
.And.Pass();
}
}
[Fact]
public void When_assets_file_is_locked_by_NuGet_Then_CLI_retries_launching_the_command_for_at_least_one_second()
{
var testInstance = TestAssets.Get("AppWithToolDependency")
.CreateInstance()
.WithSourceFiles()
.WithRestoreFiles();
var assetsFile = testInstance.Root.GetDirectory("obj").GetFile("project.assets.json");
using (assetsFile.NuGetLock()
.DisposeAfter(TimeSpan.FromMilliseconds(1000)))
{
new PortableCommand()
.WithWorkingDirectory(testInstance.Root)
.ExecuteWithCapturedOutput()
.Should().HaveStdOutContaining("Hello Portable World!" + Environment.NewLine)
.And.NotHaveStdErr()
.And.Pass();
}
}
class HelloCommand : TestCommand
{
public HelloCommand()