Skip to content

Commit fb29abd

Browse files
committed
Release 0.16.1. Added an internal semaphore to CachedNameApi to synchronize access across threads.
1 parent 174ffc4 commit fb29abd

File tree

2 files changed

+97
-78
lines changed

2 files changed

+97
-78
lines changed

src/Cache/CachedNameApi.cs

+92-77
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Ipfs;
33
using Ipfs.CoreApi;
44
using OwlCore.ComponentModel;
5+
using OwlCore.Extensions;
56
using OwlCore.Storage;
67

78
namespace OwlCore.Kubo.Cache;
@@ -14,16 +15,18 @@ namespace OwlCore.Kubo.Cache;
1415
/// </remarks>
1516
public class CachedNameApi : SettingsBase, INameApi, IDelegable<INameApi>, IFlushable
1617
{
18+
private readonly SemaphoreSlim _cacheUpdateMutex = new(1, 1);
19+
1720
/// <summary>
1821
/// The cached record for a published path name in a <see cref="CachedNameApi"/>.
1922
/// </summary>
2023
public record PublishedPathName(string path, bool resolve, string key, TimeSpan? lifetime, NamedContent returnValue);
21-
24+
2225
/// <summary>
2326
/// The cached record for a published cid name in a <see cref="CachedNameApi"/>.
2427
/// </summary>
2528
public record PublishedCidName(Cid id, string key, TimeSpan? lifetime, NamedContent returnValue);
26-
29+
2730
/// <summary>
2831
/// The cached record for a resolved name in a <see cref="CachedNameApi"/>.
2932
/// </summary>
@@ -80,73 +83,82 @@ public List<PublishedPathName> PublishedStringNamedContent
8083
/// <param name="cancellationToken">A token that can be used to cancel the ongoing operation.</param>
8184
public async Task FlushAsync(CancellationToken cancellationToken = default)
8285
{
83-
foreach (var item in PublishedCidNamedContent.ToArray())
86+
using (await _cacheUpdateMutex.DisposableWaitAsync(cancellationToken))
8487
{
85-
cancellationToken.ThrowIfCancellationRequested();
88+
foreach (var item in PublishedCidNamedContent)
89+
{
90+
cancellationToken.ThrowIfCancellationRequested();
8691

87-
Console.WriteLine($"Flushing key {item.key} with value {item.id}");
92+
Console.WriteLine($"Flushing key {item.key} with value {item.id}");
8893

89-
// Publish to ipfs
90-
var result = await Inner.PublishAsync(item.id, item.key, item.lifetime, cancellationToken);
94+
// Publish to ipfs
95+
var result = await Inner.PublishAsync(item.id, item.key, item.lifetime, cancellationToken);
9196

92-
// Verify result matches original returned data
93-
_ = Guard.Equals(result.ContentPath, item.returnValue.ContentPath);
94-
_ = Guard.Equals(result.NamePath, item.returnValue.NamePath);
97+
// Verify result matches original returned data
98+
_ = Guard.Equals(result.ContentPath, item.returnValue.ContentPath);
99+
_ = Guard.Equals(result.NamePath, item.returnValue.NamePath);
95100

96-
// Update cache
97-
PublishedCidNamedContent.Remove(item);
98-
PublishedCidNamedContent.Add(item with { returnValue = result });
99-
}
101+
// Update cache
102+
PublishedCidNamedContent.Remove(item);
103+
PublishedCidNamedContent.Add(item with { returnValue = result });
104+
}
100105

101-
foreach (var item in PublishedStringNamedContent)
102-
{
103-
cancellationToken.ThrowIfCancellationRequested();
106+
foreach (var item in PublishedStringNamedContent)
107+
{
108+
cancellationToken.ThrowIfCancellationRequested();
104109

105-
Console.WriteLine($"Flushing key {item.key} with value {item.path}");
110+
Console.WriteLine($"Flushing key {item.key} with value {item.path}");
106111

107-
// Publish to ipfs
108-
var result = await Inner.PublishAsync(item.path, item.resolve, item.key, item.lifetime, cancellationToken);
112+
// Publish to ipfs
113+
var result = await Inner.PublishAsync(item.path, item.resolve, item.key, item.lifetime, cancellationToken);
109114

110-
// Verify result matches original returned data
111-
_ = Guard.Equals(result.ContentPath, item.returnValue.ContentPath);
112-
_ = Guard.Equals(result.NamePath, item.returnValue.NamePath);
115+
// Verify result matches original returned data
116+
_ = Guard.Equals(result.ContentPath, item.returnValue.ContentPath);
117+
_ = Guard.Equals(result.NamePath, item.returnValue.NamePath);
113118

114-
// Update cache
115-
PublishedStringNamedContent.Remove(item);
116-
PublishedStringNamedContent.Add(item with { returnValue = result });
119+
// Update cache
120+
PublishedStringNamedContent.Remove(item);
121+
PublishedStringNamedContent.Add(item with { returnValue = result });
122+
}
117123
}
118124
}
119125

120126
/// <inheritdoc />
121127
public async Task<NamedContent> PublishAsync(string path, bool resolve = true, string key = "self", TimeSpan? lifetime = null, CancellationToken cancel = default)
122128
{
123-
if (PublishedStringNamedContent.FirstOrDefault(x => x.key == key) is { } existing)
124-
PublishedStringNamedContent.Remove(existing);
129+
using (await _cacheUpdateMutex.DisposableWaitAsync(cancel))
130+
{
131+
if (PublishedStringNamedContent.FirstOrDefault(x => x.key == key) is { } existing)
132+
PublishedStringNamedContent.Remove(existing);
125133

126-
var keys = await KeyApi.ListAsync(cancel);
127-
var existingKey = keys.FirstOrDefault(x => x.Name == key);
128-
var keyId = existingKey?.Id;
134+
var keys = await KeyApi.ListAsync(cancel);
135+
var existingKey = keys.FirstOrDefault(x => x.Name == key);
136+
var keyId = existingKey?.Id;
129137

130-
NamedContent published = new() { ContentPath = path, NamePath = $"/ipns/{keyId}" };
138+
NamedContent published = new() { ContentPath = path, NamePath = $"/ipns/{keyId}" };
131139

132-
PublishedStringNamedContent.Add(new(path, resolve, key, lifetime, published));
133-
return published;
140+
PublishedStringNamedContent.Add(new(path, resolve, key, lifetime, published));
141+
return published;
142+
}
134143
}
135144

136145
/// <inheritdoc />
137146
public async Task<NamedContent> PublishAsync(Cid id, string key = "self", TimeSpan? lifetime = null, CancellationToken cancel = default)
138147
{
139-
if (PublishedCidNamedContent.FirstOrDefault(x => x.key == key) is { } existing)
140-
PublishedCidNamedContent.Remove(existing);
148+
using (await _cacheUpdateMutex.DisposableWaitAsync(cancel))
149+
{
150+
if (PublishedCidNamedContent.FirstOrDefault(x => x.key == key) is { } existing)
151+
PublishedCidNamedContent.Remove(existing);
141152

142-
var keys = await KeyApi.ListAsync(cancel);
143-
var existingKey = keys.FirstOrDefault(x => x.Name == key);
144-
var keyId = existingKey?.Id;
153+
var keys = await KeyApi.ListAsync(cancel);
154+
var existingKey = keys.FirstOrDefault(x => x.Name == key);
155+
var keyId = existingKey?.Id;
145156

146-
NamedContent published = new() { ContentPath = $"/ipfs/{id}", NamePath = $"/ipns/{keyId}" };
147-
PublishedCidNamedContent.Add(new(id, key, lifetime, published));
157+
NamedContent published = new() { ContentPath = $"/ipfs/{id}", NamePath = $"/ipns/{keyId}" };
158+
PublishedCidNamedContent.Add(new(id, key, lifetime, published));
148159

149-
return published;
160+
return published;
161+
}
150162
}
151163

152164
/// <inheritdoc />
@@ -157,49 +169,52 @@ public async Task<NamedContent> PublishAsync(Cid id, string key = "self", TimeSp
157169
/// </remarks>
158170
public async Task<string> ResolveAsync(string name, bool recursive = false, bool nocache = false, CancellationToken cancel = default)
159171
{
160-
if (nocache)
172+
using (await _cacheUpdateMutex.DisposableWaitAsync(cancel))
161173
{
162-
try
174+
if (nocache)
163175
{
164-
// Don't resolve with cache, but still save resolved data to cache.
165-
var resToCache = await Inner.ResolveAsync(name, recursive, nocache, cancel);
166-
167-
var existing = ResolvedNames.FirstOrDefault(x => x.name == name);
168-
if (existing is not null)
169-
ResolvedNames.Remove(existing);
170-
171-
ResolvedNames.Add(new(name, recursive, resToCache));
172-
173-
return resToCache;
176+
try
177+
{
178+
// Don't resolve with cache, but still save resolved data to cache.
179+
var resToCache = await Inner.ResolveAsync(name, recursive, nocache, cancel);
180+
181+
var existing = ResolvedNames.FirstOrDefault(x => x.name == name);
182+
if (existing is not null)
183+
ResolvedNames.Remove(existing);
184+
185+
ResolvedNames.Add(new(name, recursive, resToCache));
186+
187+
return resToCache;
188+
}
189+
catch
190+
{
191+
// request failed, continue with cache anyway
192+
}
174193
}
175-
catch
194+
195+
// Check if name is in published cache
196+
if (PublishedCidNamedContent.FirstOrDefault(x => x.returnValue.NamePath is not null && (name.Contains(x.returnValue.NamePath) || x.returnValue.NamePath.Contains(name))) is { } publishedCidNamedContent)
176197
{
177-
// request failed, continue with cache anyway
198+
if (publishedCidNamedContent.returnValue.ContentPath is not null)
199+
return publishedCidNamedContent.returnValue.ContentPath;
178200
}
179-
}
180201

181-
// Check if name is in published cache
182-
if (PublishedCidNamedContent.FirstOrDefault(x => x.returnValue.NamePath is not null && (name.Contains(x.returnValue.NamePath) || x.returnValue.NamePath.Contains(name))) is { } publishedCidNamedContent)
183-
{
184-
if (publishedCidNamedContent.returnValue.ContentPath is not null)
185-
return publishedCidNamedContent.returnValue.ContentPath;
186-
}
187-
188-
// Check in other published cache
189-
if (PublishedStringNamedContent.FirstOrDefault(x => x.returnValue.NamePath is not null && (name.Contains(x.returnValue.NamePath) || x.returnValue.NamePath.Contains(name))) is { } publishedStringNamedContent)
190-
{
191-
if (publishedStringNamedContent.returnValue.ContentPath is not null)
192-
return publishedStringNamedContent.returnValue.ContentPath;
193-
}
202+
// Check in other published cache
203+
if (PublishedStringNamedContent.FirstOrDefault(x => x.returnValue.NamePath is not null && (name.Contains(x.returnValue.NamePath) || x.returnValue.NamePath.Contains(name))) is { } publishedStringNamedContent)
204+
{
205+
if (publishedStringNamedContent.returnValue.ContentPath is not null)
206+
return publishedStringNamedContent.returnValue.ContentPath;
207+
}
194208

195-
// Check if previously resolved.
196-
if (ResolvedNames.FirstOrDefault(x => x.name == name) is { } resolvedName)
197-
return resolvedName.returnValue;
209+
// Check if previously resolved.
210+
if (ResolvedNames.FirstOrDefault(x => x.name == name) is { } resolvedName)
211+
return resolvedName.returnValue;
198212

199-
// If not, resolve the name and cache.
200-
var result = await Inner.ResolveAsync(name, recursive, nocache, cancel);
201-
ResolvedNames.Add(new(name, recursive, result));
213+
// If not, resolve the name and cache.
214+
var result = await Inner.ResolveAsync(name, recursive, nocache, cancel);
215+
ResolvedNames.Add(new(name, recursive, result));
202216

203-
return result;
217+
return result;
218+
}
204219
}
205220
}

src/OwlCore.Kubo.csproj

+5-1
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@
1414
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
1515

1616
<Author>Arlo Godfrey</Author>
17-
<Version>0.16.0</Version>
17+
<Version>0.16.1</Version>
1818
<Product>OwlCore</Product>
1919
<Description>
2020
An essential toolkit for Kubo, IPFS and the distributed web.
2121
</Description>
2222
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
2323
<PackageReleaseNotes>
24+
--- 0.16.1 ---
25+
[Fixes]
26+
Added an internal semaphore to CachedNameApi to synchronize access across threads.
27+
2428
--- 0.16.0 ---
2529
[Breaking]
2630
Inherited breaking changes from IpfsShipyard.Net.Http.Client 0.2.0.

0 commit comments

Comments
 (0)