Scope.cs
1 //
2 // Copyright (c) Microsoft. All rights reserved.
3 // Licensed under the MIT license.
4 //
5 // Microsoft Bot Framework: http://botframework.com
6 //
7 // Bot Builder SDK GitHub:
8 // https://github.com/Microsoft/BotBuilder
9 //
10 // Copyright (c) Microsoft Corporation
11 // All rights reserved.
12 //
13 // MIT License:
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 //
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 //
25 // THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33 
34 using System;
35 using System.Collections.Generic;
36 using System.Linq;
37 using System.Text;
38 using System.Threading;
39 using System.Threading.Tasks;
40 
41 namespace Microsoft.Bot.Builder.Internals.Fibers
42 {
47  public interface IScope<T>
48  {
55  Task<IDisposable> WithScopeAsync(T item, CancellationToken token);
56  }
57 
58  public sealed class LocalMutualExclusion<T> : IScope<T>
59  where T : class
60  {
61  private sealed class KeyedGate
62  {
63  // this is the per-item semaphore
64  public readonly SemaphoreSlim Gate = new SemaphoreSlim(initialCount: 1, maxCount: 1);
65 
66  // this property is protected by a monitor around gateByItem
67  public int ReferenceCount = 0;
68  }
69 
70  private readonly Dictionary<T, KeyedGate> gateByItem;
71 
72  public LocalMutualExclusion(IEqualityComparer<T> comparer)
73  {
74  this.gateByItem = new Dictionary<T, KeyedGate>(comparer);
75  }
76 
77  public bool TryGetReferenceCount(T item, out int referenceCount)
78  {
79  lock (this.gateByItem)
80  {
81  KeyedGate gate;
82  if (this.gateByItem.TryGetValue(item, out gate))
83  {
84  referenceCount = gate.ReferenceCount;
85  return true;
86  }
87  }
88 
89  referenceCount = 0;
90  return false;
91  }
92 
93  async Task<IDisposable> IScope<T>.WithScopeAsync(T item, CancellationToken token)
94  {
95  // manage reference count under global mutex
96  KeyedGate gate;
97  lock (this.gateByItem)
98  {
99  gate = this.gateByItem.GetOrAdd(item, _ => new KeyedGate());
100  ++gate.ReferenceCount;
101  }
102 
103  // wait to enter this item's semaphore outside of global mutex
104  await gate.Gate.WaitAsync(token);
105 
106  return new Releaser(this, item);
107  }
108 
109  private sealed class Releaser : IDisposable
110  {
111  private readonly LocalMutualExclusion<T> owner;
112  private readonly T item;
113  public Releaser(LocalMutualExclusion<T> owner, T item)
114  {
115  SetField.NotNull(out this.owner, nameof(owner), owner);
116  SetField.NotNull(out this.item, nameof(item), item);
117  }
118  public void Dispose()
119  {
120  KeyedGate gate;
121  lock (this.owner.gateByItem)
122  {
123  gate = this.owner.gateByItem[this.item];
124  }
125 
126  // exit this item's semaphore outside of global mutex, and let other threads run here
127  gate.Gate.Release();
128 
129  // obtain the global mutex to update the reference count
130  lock (this.owner.gateByItem)
131  {
132  --gate.ReferenceCount;
133 
134  // and possibly clean up the semaphore
135  // if there are no other threads referencing this item's semaphore
136  if (gate.ReferenceCount == 0)
137  {
138  if (!this.owner.gateByItem.Remove(this.item))
139  {
140  throw new InvalidOperationException();
141  }
142  }
143  }
144  }
145  }
146  }
147 }
bool TryGetReferenceCount(T item, out int referenceCount)
Definition: Scope.cs:77
Task< IDisposable > WithScopeAsync(T item, CancellationToken token)
Enter a scope of code keyed by item.
LocalMutualExclusion(IEqualityComparer< T > comparer)
Definition: Scope.cs:72
Provide an abstraction to serialize access to an item for a using-block scope of code.
Definition: Scope.cs:47