|
43 | 43 |
|
44 | 44 | namespace MonoTorrent.Client
|
45 | 45 | {
|
46 |
| - class TrackerWithConnection : Tracker |
| 46 | + class CustomTracker : Tracker |
47 | 47 | {
|
48 |
| - public CustomTracker Connection { get; } |
| 48 | + public CustomTrackerConnection Connection { get; } |
49 | 49 |
|
50 |
| - public TrackerWithConnection (ITrackerConnection connection) |
| 50 | + public CustomTracker (CustomTrackerConnection connection) |
51 | 51 | : base (connection)
|
52 | 52 | {
|
53 |
| - Connection = (CustomTracker) connection; |
| 53 | + Connection = connection; |
54 | 54 | }
|
55 | 55 | }
|
56 |
| - class DefaultTracker : ITrackerConnection |
| 56 | + |
| 57 | + class RateLimitingTracker : Tracker |
| 58 | + { |
| 59 | + public RateLimitingTrackerConnection Connection { get; } |
| 60 | + |
| 61 | + public RateLimitingTracker (RateLimitingTrackerConnection connection) |
| 62 | + : base (connection) |
| 63 | + { |
| 64 | + Connection = (RateLimitingTrackerConnection) connection; |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + class RateLimitingTrackerConnection : ITrackerConnection |
57 | 69 | {
|
58 | 70 | public bool CanScrape => true;
|
59 | 71 |
|
60 | 72 | public Uri Uri => new Uri ("http://tracker:5353/announce");
|
61 | 73 |
|
62 |
| - public DefaultTracker () |
| 74 | + public List<TaskCompletionSource<TrackerState>> PendingAnnounces { get; } = new List<TaskCompletionSource<TrackerState>> (); |
| 75 | + public List<TaskCompletionSource<TrackerState>> PendingScrapes { get; } = new List<TaskCompletionSource<TrackerState>> (); |
| 76 | + |
| 77 | + public RateLimitingTrackerConnection () |
63 | 78 | {
|
64 | 79 |
|
65 | 80 | }
|
66 | 81 |
|
67 |
| - public ReusableTask<AnnounceResponse> AnnounceAsync (AnnounceRequest parameters, CancellationToken token) |
| 82 | + public async ReusableTask<AnnounceResponse> AnnounceAsync (AnnounceRequest parameters, CancellationToken token) |
68 | 83 | {
|
69 |
| - return ReusableTask.FromResult (new AnnounceResponse (TrackerState.Ok)); |
| 84 | + var tcs = new TaskCompletionSource<TrackerState> (); |
| 85 | + PendingAnnounces.Add (tcs); |
| 86 | + return new AnnounceResponse (await tcs.Task); |
70 | 87 | }
|
71 | 88 |
|
72 |
| - public ReusableTask<ScrapeResponse> ScrapeAsync (ScrapeRequest parameters, CancellationToken token) |
| 89 | + public async ReusableTask<ScrapeResponse> ScrapeAsync (ScrapeRequest parameters, CancellationToken token) |
73 | 90 | {
|
74 |
| - return ReusableTask.FromResult (new ScrapeResponse (TrackerState.Ok)); |
| 91 | + var tcs = new TaskCompletionSource<TrackerState> (); |
| 92 | + PendingScrapes.Add (tcs); |
| 93 | + return new ScrapeResponse (await tcs.Task); |
75 | 94 | }
|
76 | 95 | }
|
77 | 96 |
|
@@ -110,17 +129,17 @@ public ScrapeRequest CreateScrape ()
|
110 | 129 | };
|
111 | 130 |
|
112 | 131 | TrackerManager trackerManager;
|
113 |
| - IList<List<TrackerWithConnection>> trackers; |
| 132 | + IList<List<CustomTracker>> trackers; |
114 | 133 |
|
115 | 134 | Factories Factories { get; set; }
|
116 | 135 |
|
117 | 136 | [SetUp]
|
118 | 137 | public void Setup ()
|
119 | 138 | {
|
120 | 139 | Factories = Factories.Default
|
121 |
| - .WithTrackerCreator ("custom", uri => new TrackerWithConnection (new CustomTracker (uri))); |
| 140 | + .WithTrackerCreator ("custom", uri => new CustomTracker (new CustomTrackerConnection (uri))); |
122 | 141 | trackerManager = new TrackerManager (Factories, new RequestFactory (), trackerUrls, true);
|
123 |
| - trackers = trackerManager.Tiers.Select (t => t.Trackers.Cast<TrackerWithConnection> ().ToList ()).ToList (); |
| 142 | + trackers = trackerManager.Tiers.Select (t => t.Trackers.Cast<CustomTracker> ().ToList ()).ToList (); |
124 | 143 | }
|
125 | 144 |
|
126 | 145 | [Test]
|
@@ -231,6 +250,47 @@ public async Task AnnounceFailed ()
|
231 | 250 | Assert.AreEqual (0, trackers[1][1].Connection.AnnouncedAt.Count, "#8");
|
232 | 251 | }
|
233 | 252 |
|
| 253 | + [Test] |
| 254 | + public async Task Announce_RateLimitedAnnounceAttempts () |
| 255 | + { |
| 256 | + var factories = Factories.Default |
| 257 | + .WithTrackerCreator ("custom", uri => new RateLimitingTracker (new RateLimitingTrackerConnection ())); |
| 258 | + |
| 259 | + var tier = new[] { new[] { $"custom://tracker/announce" } }; |
| 260 | + var trackerManager = new TrackerManager (factories, new RequestFactory (), tier, true); |
| 261 | + var trackers = trackerManager.Tiers.Select (t => t.Trackers.Cast<RateLimitingTracker> ().ToList ()).ToList (); |
| 262 | + |
| 263 | + // only 1 concurrent regular announce can run at a time. |
| 264 | + var announce = trackerManager.AnnounceAsync (CancellationToken.None); |
| 265 | + |
| 266 | + // These should all early-exit |
| 267 | + for (int i = 0; i < 3; i++) |
| 268 | + await trackerManager.AnnounceAsync (CancellationToken.None).WithTimeout (TimeSpan.FromSeconds (10)); |
| 269 | + for (int i = 0; i < 3; i++) |
| 270 | + await trackerManager.AnnounceAsync (TorrentEvent.None, CancellationToken.None).WithTimeout (TimeSpan.FromSeconds (10)); |
| 271 | + |
| 272 | + Assert.IsFalse (announce.IsCompleted); |
| 273 | + } |
| 274 | + |
| 275 | + [Test] |
| 276 | + public void Announce_RateLimitedTierAnnounces () |
| 277 | + { |
| 278 | + var factories = Factories.Default |
| 279 | + .WithTrackerCreator ("custom", uri => new RateLimitingTracker (new RateLimitingTrackerConnection ())); |
| 280 | + |
| 281 | + // Create 100 tracker tiers. |
| 282 | + var urls = Enumerable.Range (0, 100).Select (t => new[] { $"custom://tracker{t}/announce" }).ToArray (); |
| 283 | + var trackerManager = new TrackerManager (factories, new RequestFactory (), urls, true); |
| 284 | + var trackers = trackerManager.Tiers.Select (t => t.Trackers.Cast<RateLimitingTracker> ().ToList ()).ToList (); |
| 285 | + |
| 286 | + var cts = new CancellationTokenSource (TimeSpan.FromSeconds (10)); |
| 287 | + var announce = trackerManager.AnnounceAsync (CancellationToken.None); |
| 288 | + while (trackers.SelectMany (t => t).Where (t => t.Connection.PendingAnnounces.Count == 1).Count () != 15) { |
| 289 | + Thread.Sleep (1); |
| 290 | + cts.Token.ThrowIfCancellationRequested (); |
| 291 | + } |
| 292 | + } |
| 293 | + |
234 | 294 | [Test]
|
235 | 295 | public async Task CurrentTracker ()
|
236 | 296 | {
|
@@ -273,7 +333,7 @@ public async Task AnnounceTwice_SendStartedOnce ()
|
273 | 333 | [Test]
|
274 | 334 | public void Defaults ()
|
275 | 335 | {
|
276 |
| - var tracker = new Tracker (new DefaultTracker ()); |
| 336 | + var tracker = new CustomTracker (new CustomTrackerConnection (new Uri("http://tester/announce"))); |
277 | 337 | Assert.AreEqual (TimeSpan.FromMinutes (3), tracker.MinUpdateInterval, "#1");
|
278 | 338 | Assert.AreEqual (TimeSpan.FromMinutes (30), tracker.UpdateInterval, "#2");
|
279 | 339 | Assert.IsNotNull (tracker.WarningMessage, "#3");
|
|
0 commit comments