Skip to content

Commit 25b4a8b

Browse files
committed
ticket queue extended
- priority tickets - improved keepalive
1 parent d269a9b commit 25b4a8b

11 files changed

+108
-36
lines changed

docs/development.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ If you want to develop Linux on Windows, Vagrant is an excellent and convenient
1616
- Dotnet 6.0 SDK
1717
- Visual Studio 2019 (Windows) or Visual Studio Code with C# extension. If you're using Visual Studio make sure you've update to the latest version. Visual Studio 2017 isn't supported.
1818

19-
Unit test coverage is done with https://github.com/SteveGilham/altcover, install ReportGenerator with
20-
21-
dotnet tool install --global dotnet-reportgenerator-globaltool
19+
Unit test coverage is done with https://github.com/SteveGilham/altcover, install ReportGenerator. To get a version compatible with this project run
20+
21+
dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.3.11
2222

2323
## Running from Visual Studio
2424

src/Tetrifact.Core/IProcessManager.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ public interface IProcessManager
1414
bool AnyOfKeyExists(string key);
1515

1616
void AddUnique(ProcessCategories category, string key);
17-
17+
18+
void AddUnique(ProcessCategories category, string key, string metadata);
19+
1820
void AddUnique(ProcessCategories category, string key, TimeSpan timespan);
1921

2022
void AddUnique(ProcessCategories category, string key, string metadata, TimeSpan timespan);

src/Tetrifact.Core/ISettings.cs

+5
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ public interface ISettings
120120
/// </summary>
121121
int DownloadQueueTicketLifespan { get; set; }
122122

123+
/// <summary>
124+
/// Static tickets which can be used to bypass the regular download queue.
125+
/// </summary>
126+
IEnumerable<string> DownloadQueuePriorityTickets { get; set; }
127+
123128
/// <summary>
124129
/// Minimum amount of free space (megabytes) on storage drive - if less is available, new uploads will fail.
125130
/// </summary>

src/Tetrifact.Core/ProcessItem.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@ public class ProcessItem
2020
public ProcessCategories Category { get; set; }
2121

2222
/// <summary>
23-
/// If set, the time lock item was created. If not set, lock never times out
23+
/// If set, the time item was created.
2424
/// </summary>
2525
public DateTime? AddedUTC { get; set; }
2626

27+
/// <summary>
28+
/// If set, the time item was kept alive. If not set, lock never times out.
29+
/// </summary>
30+
public DateTime? KeepAliveUtc { get; set; }
31+
2732
/// <summary>
2833
/// If set, the max age lock item can have before being auto purged.
2934
/// </summary>
@@ -44,7 +49,7 @@ public ProcessItem Clone()
4449
/// <returns></returns>
4550
public override string ToString()
4651
{
47-
return $"{{{this.GetType().Name} Id:{Id}, MetaData:{Metadata}, Category:{Category}, AddedUtc:{AddedUTC}, MaxLifespace:{MaxLifespan} }}";
52+
return $"{{{this.GetType().Name} Id:{Id}, MetaData:{Metadata}, Category:{Category}, AddedUtc:{AddedUTC}, KeepAliveUtc:{KeepAliveUtc}, MaxLifespace:{MaxLifespan} }}";
4853
}
4954
}
5055
}

src/Tetrifact.Core/ProcessManager.cs

+51-14
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,28 @@ public void AddUnique(ProcessCategories category, string key)
6969
if (_items.ContainsKey(key))
7070
return;
7171

72-
_items.Add(key, new ProcessItem { Id = key, Category = category });
72+
_items.Add(key, new ProcessItem {
73+
Id = key,
74+
AddedUTC = DateTime.UtcNow,
75+
Category = category });
76+
77+
_log.LogInformation($"Created process, category {category}, id {key}, no lifespan limit.");
78+
}
79+
}
80+
81+
public void AddUnique(ProcessCategories category, string key, string metadata)
82+
{
83+
lock (_items)
84+
{
85+
if (_items.ContainsKey(key))
86+
return;
87+
88+
_items.Add(key, new ProcessItem {
89+
Id = key,
90+
AddedUTC = DateTime.UtcNow,
91+
Category = category,
92+
Metadata = metadata });
93+
7394
_log.LogInformation($"Created process, category {category}, id {key}, no lifespan limit.");
7495
}
7596
}
@@ -81,7 +102,14 @@ public void AddUnique(ProcessCategories category, string key, string metadata, T
81102
if (_items.ContainsKey(key))
82103
return;
83104

84-
_items.Add(key, new ProcessItem { Id = key, Metadata = metadata, AddedUTC = DateTime.UtcNow, MaxLifespan = timespan, Category = category });
105+
_items.Add(key, new ProcessItem {
106+
Id = key,
107+
Metadata = metadata,
108+
AddedUTC = DateTime.UtcNow,
109+
KeepAliveUtc = DateTime.UtcNow,
110+
MaxLifespan = timespan,
111+
Category = category });
112+
85113
_log.LogInformation($"Created process, category {category}, id {key}, metadata {metadata}, forced lifespan {timespan}.");
86114
}
87115
}
@@ -93,7 +121,13 @@ public void AddUnique(ProcessCategories category, string key, TimeSpan timespan)
93121
if (_items.ContainsKey(key))
94122
return;
95123

96-
_items.Add(key, new ProcessItem{ Id = key, AddedUTC = DateTime.UtcNow, MaxLifespan = timespan, Category = category });
124+
_items.Add(key, new ProcessItem{
125+
Id = key,
126+
AddedUTC = DateTime.UtcNow,
127+
KeepAliveUtc = DateTime.UtcNow,
128+
MaxLifespan = timespan,
129+
Category = category });
130+
97131
_log.LogInformation($"Created process, category {category}, id {key}, forced lifespan {timespan}.");
98132
}
99133
}
@@ -133,25 +167,28 @@ public void KeepAlive(string key)
133167
if (!_items.ContainsKey(key))
134168
return;
135169

136-
_items[key].AddedUTC = DateTime.UtcNow;
170+
_items[key].KeepAliveUtc = DateTime.UtcNow;
137171
}
138172
}
139173

140174
public void ClearExpired()
141175
{
142-
for (int i = 0 ; i < _items.Count; i ++)
176+
lock (_items)
143177
{
144-
string key = _items.Keys.ElementAt(_items.Count - 1 - i);
145-
if (!_items[key].AddedUTC.HasValue || !_items[key].MaxLifespan.HasValue)
146-
continue;
178+
for (int i = 0; i < _items.Count; i++)
179+
{
180+
string key = _items.Keys.ElementAt(_items.Count - 1 - i);
181+
if (!_items[key].KeepAliveUtc.HasValue || !_items[key].MaxLifespan.HasValue)
182+
continue;
147183

148-
TimeSpan ts = _items[key].MaxLifespan.Value;
149-
DateTime dt = _items[key].AddedUTC.Value;
150-
if (DateTime.UtcNow - dt < ts)
151-
continue;
184+
TimeSpan ts = _items[key].MaxLifespan.Value;
185+
DateTime dt = _items[key].KeepAliveUtc.Value;
186+
if (DateTime.UtcNow - dt < ts)
187+
continue;
152188

153-
_items.Remove(key);
154-
_log.LogInformation($"Processes id {key} timed out and removed.");
189+
_items.Remove(key);
190+
_log.LogInformation($"Processes id {key} timed out and removed.");
191+
}
155192
}
156193
}
157194

src/Tetrifact.Core/Settings.cs

+3
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ public class Settings : ISettings
9696

9797
public int DownloadQueueTicketLifespan { get; set; }
9898

99+
public IEnumerable<string> DownloadQueuePriorityTickets { get; set; }
100+
99101
/// <summary>
100102
///
101103
/// </summary>
@@ -112,6 +114,7 @@ public Settings()
112114
// defaults
113115
this.DownloadQueueTicketLifespan = 10; // seconds
114116
this.AccessTokens = new List<string>();
117+
this.DownloadQueuePriorityTickets = new List<string>();
115118
this.PackageDeleteEnabled = true;
116119
this.PackageCreateEnabled = true;
117120
this.ArchiveCPUThreads = 4; // for compression solutions that support multithreading.

src/Tetrifact.Web/Controllers/ArchivesController.cs

+22-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using Microsoft.AspNetCore.Http.Headers;
33
using Microsoft.AspNetCore.Mvc;
44
using Microsoft.Extensions.Logging;
5-
using Newtonsoft.Json.Linq;
65
using System;
76
using System.Collections.Generic;
87
using System.IO;
@@ -99,19 +98,28 @@ public ActionResult GetArchive(string packageId, [FromQuery(Name = "ticket")] st
9998
RequestHeaders headers = Request.GetTypedHeaders();
10099
bool isLocal = headers.Host.ToString().ToLower() == "localhost";
101100

101+
// local (this website) downloads always allowed.
102102
if (!isLocal && _settings.MaximumSimultaneousDownloads.HasValue)
103103
{
104104
if (string.IsNullOrEmpty(ticket))
105105
return Responses.NoTicket();
106106

107-
IEnumerable<ProcessItem> userTickets = _processManager.GetByCategory(ProcessCategories.ArchiveQueueSlot);
108-
ProcessItem userTicket = userTickets.FirstOrDefault(i => i.Id == ticket);
109-
if (userTicket == null)
110-
return Responses.NoTicket();
111-
112-
int count = userTickets.Where(t => t.AddedUTC < userTicket.AddedUTC).Count();
113-
if (count > _settings.MaximumSimultaneousDownloads)
114-
return Responses.QueueFull(count, _settings.MaximumSimultaneousDownloads.Value);
107+
// check for priority tickets
108+
if (_settings.DownloadQueuePriorityTickets.Contains(ticket))
109+
{
110+
_log.LogInformation($"Priority ticket {ticket} used from host {headers.Host}");
111+
}
112+
else
113+
{
114+
IEnumerable<ProcessItem> userTickets = _processManager.GetByCategory(ProcessCategories.ArchiveQueueSlot);
115+
ProcessItem userTicket = userTickets.FirstOrDefault(i => i.Id == ticket);
116+
if (userTicket == null)
117+
return Responses.NoTicket();
118+
119+
int count = userTickets.Where(t => t.AddedUTC < userTicket.AddedUTC).Count();
120+
if (count > _settings.MaximumSimultaneousDownloads)
121+
return Responses.QueueFull(count, _settings.MaximumSimultaneousDownloads.Value);
122+
}
115123
}
116124

117125
string archivePath = _archiveService.GetPackageArchivePath(packageId);
@@ -135,7 +143,11 @@ public ActionResult GetArchive(string packageId, [FromQuery(Name = "ticket")] st
135143
_processManager.KeepAlive(ticket);
136144
};
137145

138-
return File(progressableStream, "application/octet-stream", $"{packageId}.zip", enableRangeProcessing: true);
146+
return File(
147+
progressableStream,
148+
"application/octet-stream",
149+
$"{packageId}.zip",
150+
enableRangeProcessing: true);
139151
}
140152
catch (PackageNotFoundException ex)
141153
{

src/Tetrifact.Web/Controllers/TicketsController.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,16 @@ public ActionResult CreateQueueTicket(string requestIdentifier)
5959
});
6060

6161
string ticket = Guid.NewGuid().ToString();
62-
_processManager.AddUnique(ProcessCategories.ArchiveQueueSlot, ticket, requestIdentifier, new TimeSpan(0, 0, _settings.DownloadQueueTicketLifespan));
62+
_processManager.AddUnique(
63+
ProcessCategories.ArchiveQueueSlot,
64+
ticket,
65+
requestIdentifier);
6366

6467
return new JsonResult(new
6568
{
6669
success = new
6770
{
68-
ticket = ticket,
71+
ticket,
6972
clientIdentifier = requestIdentifier,
7073
required = true
7174
}

src/Tetrifact.Web/Core/Daemons/ProcessManagerCron.cs

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using Microsoft.Extensions.Logging;
22
using System.Threading.Tasks;
3-
using System;
43
using Tetrifact.Core;
54

65
namespace Tetrifact.Web

src/Tetrifact.Web/Views/Home/Processes.cshtml

+7
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@
2727
at @process.AddedUTC.ToHumanString()
2828
</text>
2929
}
30+
31+
@process.KeepAliveUtc.HasValue
32+
{
33+
<text>
34+
at @process.KeepAliveUtc.ToHumanString()
35+
</text>
36+
}
3037
</span>
3138
</li>
3239
}

src/cover.bat

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
:: Convenient single-click script to run cover. Works on Windows only.
22

33
dotnet test /p:AltCover=true
4-
pause
5-
:: reportgenerator -reports:./Tetrifact.Tests/coverage.xml -targetdir:./Tetrifact.Tests/coverage -assemblyfilters:+Tetrifact.*;-Tetrifact.Tests -classfilters:-Tetrifact.Core.ThreadDefault;-Tetrifact.Web.DaemonProcessRunner;-Tetrifact.Web.Pager;-Tetrifact.Web.Program;-Tetrifact.Web.Startup;-Tetrifact.Web.ReadLevel;-Tetrifact.Web.WriteLevel;-*f__*
6-
:: start Tetrifact.Tests\coverage\index.html
4+
reportgenerator -reports:./Tetrifact.Tests/coverage.xml -targetdir:./Tetrifact.Tests/coverage -assemblyfilters:+Tetrifact.*;-Tetrifact.Tests -classfilters:-Tetrifact.Core.ThreadDefault;-Tetrifact.Web.DaemonProcessRunner;-Tetrifact.Web.Pager;-Tetrifact.Web.Program;-Tetrifact.Web.Startup;-Tetrifact.Web.ReadLevel;-Tetrifact.Web.WriteLevel;-*f__*
5+
start Tetrifact.Tests\coverage\index.html

0 commit comments

Comments
 (0)