Skip to content
This repository was archived by the owner on Oct 4, 2021. It is now read-only.

Commit 45c5566

Browse files
committed
Fixes VSTS Bug 1002614: System.ObjectDisposedException exception in
MonoDevelop.VersionControl.Git.GitRepository.EnsureInitialized() https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1002614 Some operations need to be canceled on Dispose.
1 parent a6bc297 commit 45c5566

File tree

2 files changed

+72
-50
lines changed
  • main/src/addins/VersionControl

2 files changed

+72
-50
lines changed

main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1888,15 +1888,17 @@ string GetCommitTextContent (Commit c, FilePath file, LibGit2Sharp.Repository re
18881888

18891889
public async Task<string> GetCurrentRemoteAsync (CancellationToken cancellationToken = default)
18901890
{
1891-
var headRemote = await RunOperationAsync (() => RootRepository.Head?.RemoteName).ConfigureAwait (false);
1892-
if (!string.IsNullOrEmpty (headRemote))
1893-
return headRemote;
1891+
using (LinkTokenToDispose (ref cancellationToken)) {
1892+
var headRemote = await RunOperationAsync (() => RootRepository.Head?.RemoteName).ConfigureAwait (false);
1893+
if (!string.IsNullOrEmpty (headRemote))
1894+
return headRemote;
18941895

1895-
var remotes = await GetRemoteNamesAsync (cancellationToken).ConfigureAwait (false);
1896-
if (remotes.Count == 0)
1897-
return null;
1896+
var remotes = await GetRemoteNamesAsync (cancellationToken).ConfigureAwait (false);
1897+
if (remotes.Count == 0)
1898+
return null;
18981899

1899-
return remotes.Contains ("origin") ? "origin" : remotes [0];
1900+
return remotes.Contains ("origin") ? "origin" : remotes [0];
1901+
}
19001902
}
19011903

19021904
public async Task PushAsync (ProgressMonitor monitor, string remote, string remoteBranch)

main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/Repository.cs

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,15 @@ protected void ShutdownScheduler ()
135135
}
136136
}
137137

138+
CancellationTokenSource disposeTokenSource = new CancellationTokenSource ();
139+
138140
public void Dispose ()
139141
{
142+
if (disposeTokenSource != null) {
143+
disposeTokenSource.Cancel ();
144+
disposeTokenSource.Dispose ();
145+
disposeTokenSource = null;
146+
}
140147
Dispose (true);
141148
GC.SuppressFinalize (this);
142149
}
@@ -292,49 +299,50 @@ public bool TryGetVersionInfo (FilePath localPath, VersionInfoQueryFlags queryFl
292299
/// </param>
293300
public async Task<IReadOnlyList<VersionInfo>> GetVersionInfoAsync (IEnumerable<FilePath> paths, VersionInfoQueryFlags queryFlags = VersionInfoQueryFlags.None, CancellationToken cancellationToken = default)
294301
{
295-
if ((queryFlags & VersionInfoQueryFlags.IgnoreCache) != 0) {
296-
var task = await ExclusiveOperationFactory.StartNew (delegate {
297-
// We shouldn't use IEnumerable because elements don't save property modifications.
298-
return OnGetVersionInfoAsync (paths, (queryFlags & VersionInfoQueryFlags.IncludeRemoteStatus) != 0, cancellationToken);
299-
}).ConfigureAwait (false);
300-
301-
var res = await task.ConfigureAwait (false);
302-
foreach (var vi in res)
303-
if (!vi.IsInitialized) await vi.InitAsync (this, cancellationToken).ConfigureAwait (false);
304-
await infoCache.SetStatusAsync (res, cancellationToken: cancellationToken).ConfigureAwait (false);
305-
return res;
306-
}
307-
var pathsToQuery = new List<FilePath> ();
308-
var result = new List<VersionInfo> ();
309-
foreach (var path in paths) {
310-
var vi = infoCache.GetStatus (path);
311-
if (vi != null) {
312-
result.Add (vi);
313-
// This status has been invalidated, query it asynchronously
314-
if (vi.RequiresRefresh)
315-
pathsToQuery.Add (path);
302+
using (LinkTokenToDispose (ref cancellationToken)) {
303+
if ((queryFlags & VersionInfoQueryFlags.IgnoreCache) != 0) {
304+
var task = await ExclusiveOperationFactory.StartNew (delegate {
305+
// We shouldn't use IEnumerable because elements don't save property modifications.
306+
return OnGetVersionInfoAsync (paths, (queryFlags & VersionInfoQueryFlags.IncludeRemoteStatus) != 0, cancellationToken);
307+
}).ConfigureAwait (false);
308+
309+
var res = await task.ConfigureAwait (false);
310+
foreach (var vi in res)
311+
if (!vi.IsInitialized) await vi.InitAsync (this, cancellationToken).ConfigureAwait (false);
312+
await infoCache.SetStatusAsync (res, cancellationToken: cancellationToken).ConfigureAwait (false);
313+
return res;
316314
}
317-
else {
318-
// If there is no cached status, query it asynchronously
319-
vi = new VersionInfo (path, "", Directory.Exists (path), VersionStatus.Versioned, null, VersionStatus.Versioned, null);
320-
await infoCache.SetStatusAsync (vi, false).ConfigureAwait (false);
321-
result.Add (vi);
322-
pathsToQuery.Add (path);
315+
var pathsToQuery = new List<FilePath> ();
316+
var result = new List<VersionInfo> ();
317+
foreach (var path in paths) {
318+
var vi = infoCache.GetStatus (path);
319+
if (vi != null) {
320+
result.Add (vi);
321+
// This status has been invalidated, query it asynchronously
322+
if (vi.RequiresRefresh)
323+
pathsToQuery.Add (path);
324+
} else {
325+
// If there is no cached status, query it asynchronously
326+
vi = new VersionInfo (path, "", Directory.Exists (path), VersionStatus.Versioned, null, VersionStatus.Versioned, null);
327+
await infoCache.SetStatusAsync (vi, false).ConfigureAwait (false);
328+
result.Add (vi);
329+
pathsToQuery.Add (path);
330+
}
331+
// Console.WriteLine ("GetVersionInfo " + string.Join (", ", paths.Select (p => p.FullPath)));
323332
}
324-
// Console.WriteLine ("GetVersionInfo " + string.Join (", ", paths.Select (p => p.FullPath)));
325-
}
326-
if (pathsToQuery.Count > 0) {
327-
ExclusiveOperationFactory.StartNew (async delegate {
328-
var status = await OnGetVersionInfoAsync (pathsToQuery, (queryFlags & VersionInfoQueryFlags.IncludeRemoteStatus) != 0, cancellationToken).ConfigureAwait (false);
329-
foreach (var vi in status) {
330-
if (!vi.IsInitialized) {
331-
await vi.InitAsync (this, cancellationToken).ConfigureAwait (false);
333+
if (pathsToQuery.Count > 0) {
334+
ExclusiveOperationFactory.StartNew (async delegate {
335+
var status = await OnGetVersionInfoAsync (pathsToQuery, (queryFlags & VersionInfoQueryFlags.IncludeRemoteStatus) != 0, cancellationToken).ConfigureAwait (false);
336+
foreach (var vi in status) {
337+
if (!vi.IsInitialized) {
338+
await vi.InitAsync (this, cancellationToken).ConfigureAwait (false);
339+
}
332340
}
333-
}
334-
await infoCache.SetStatusAsync (status, cancellationToken).ConfigureAwait (false);
335-
}).Ignore ();
341+
await infoCache.SetStatusAsync (status, cancellationToken).ConfigureAwait (false);
342+
}).Ignore ();
343+
}
344+
return result;
336345
}
337-
return result;
338346
}
339347

340348
public bool TryGetVersionInfo (IEnumerable<FilePath> paths, out IReadOnlyList<VersionInfo> infos)
@@ -360,16 +368,17 @@ public bool TryGetVersionInfo (IEnumerable<FilePath> paths, VersionInfoQueryFlag
360368
}
361369
}
362370
if (pathsToQuery.Count > 0) {
371+
var token = disposeTokenSource.Token;
363372
ExclusiveOperationFactory.StartNew (async delegate {
364373
// we don't care about initialization and setstatus async to happen on the exclusive thread, as we're not running a query here.
365-
var status = await OnGetVersionInfoAsync (paths, (queryFlags & VersionInfoQueryFlags.IncludeRemoteStatus) != 0).ConfigureAwait (false);
374+
var status = await OnGetVersionInfoAsync (paths, (queryFlags & VersionInfoQueryFlags.IncludeRemoteStatus) != 0, token).ConfigureAwait (false);
366375
foreach (var vi in status) {
367376
if (!vi.IsInitialized) {
368-
await vi.InitAsync (this).ConfigureAwait (false);
377+
await vi.InitAsync (this, token).ConfigureAwait (false);
369378
}
370379
}
371-
await infoCache.SetStatusAsync (status).ConfigureAwait (false);
372-
}).Ignore ();
380+
await infoCache.SetStatusAsync (status, token).ConfigureAwait (false);
381+
}, token).Ignore ();
373382
}
374383
if (getVersionInfoFailed) {
375384
infos = null;
@@ -379,8 +388,16 @@ public bool TryGetVersionInfo (IEnumerable<FilePath> paths, VersionInfoQueryFlag
379388
return true;
380389
}
381390

391+
protected IDisposable LinkTokenToDispose (ref CancellationToken originalToken)
392+
{
393+
var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource (originalToken, disposeTokenSource.Token);
394+
originalToken = combinedTokenSource.Token;
395+
return combinedTokenSource;
396+
}
397+
382398
public async Task<VersionInfo[]> GetDirectoryVersionInfoAsync (FilePath localDirectory, bool getRemoteStatus, bool recursive, CancellationToken cancellationToken = default)
383399
{
400+
var combinedTokenSource = LinkTokenToDispose (ref cancellationToken);
384401
try {
385402
if (recursive) {
386403
return await OnGetDirectoryVersionInfoAsync (localDirectory, getRemoteStatus, true, cancellationToken).ConfigureAwait (false);
@@ -402,6 +419,7 @@ public async Task<VersionInfo[]> GetDirectoryVersionInfoAsync (FilePath localDir
402419
return status.FileInfo;
403420
return Array.Empty<VersionInfo> ();
404421
} finally {
422+
combinedTokenSource.Dispose ();
405423
//Console.WriteLine ("GetDirectoryVersionInfo " + localDirectory + " - " + (DateTime.Now - now).TotalMilliseconds);
406424
}
407425
}
@@ -440,6 +458,7 @@ class DirectoryInfoQuery
440458
/// </param>
441459
public async Task<RevisionPath []> GetRevisionChangesAsync (Revision revision, CancellationToken cancellationToken = default)
442460
{
461+
using (LinkTokenToDispose (ref cancellationToken))
443462
using (var tracker = Instrumentation.GetRevisionChangesCounter.BeginTiming (new RepositoryMetadata (VersionControlSystem))) {
444463
try {
445464
return await OnGetRevisionChangesAsync (revision, cancellationToken).ConfigureAwait (false);
@@ -457,6 +476,7 @@ public async Task<RevisionPath []> GetRevisionChangesAsync (Revision revision, C
457476
// Returns the revision history of a file
458477
public async Task<Revision[]> GetHistoryAsync (FilePath localFile, Revision since, CancellationToken cancellationToken = default)
459478
{
479+
using (LinkTokenToDispose (ref cancellationToken))
460480
using (var tracker = Instrumentation.GetHistoryCounter.BeginTiming (new RepositoryMetadata (VersionControlSystem))) {
461481
try {
462482
return await OnGetHistoryAsync (localFile, since, cancellationToken).ConfigureAwait (false);

0 commit comments

Comments
 (0)