Skip to content

Commit

Permalink
Auto cleanup.
Browse files Browse the repository at this point in the history
  • Loading branch information
SebastianStehle committed Feb 28, 2024
1 parent 62f53cd commit 488714b
Show file tree
Hide file tree
Showing 31 changed files with 126 additions and 33 deletions.
16 changes: 15 additions & 1 deletion Demo/Callback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ public Callback(ILogger<Callback> log)
this.log = log;
}

public ValueTask OnAwarenessUpdatedAsync(ClientAwarenessEvent @event)
{
log.LogInformation("Client {clientId} awareness changed.", @event.Context.ClientId);
return default;
}

public ValueTask OnClientDisconnectedAsync(ClientDisconnectedEvent @event)
{
log.LogInformation("Client {clientId} disconnected.", @event.Context.ClientId);
return default;
}

public ValueTask OnDocumentLoadedAsync(DocumentLoadEvent @event)
{
if (@event.Context.DocumentName == "notifications")
Expand Down Expand Up @@ -69,6 +81,7 @@ await @event.Source.UpdateDocAsync(notificationCtx, (doc) =>
{
List<Notification> notifications;
// Keep the transaction open as short as possible.
using (var transaction = @event.Document.ReadTransaction())
{
notifications = newNotificationsRaw.Select(x => x.To<Notification>(transaction)).ToList();
Expand All @@ -78,7 +91,8 @@ await @event.Source.UpdateDocAsync(notificationCtx, (doc) =>
notifications = notifications.Select(x => new Notification { Text = $"You got the follow message: {x.Text}" }).ToList();
using (var transaction = doc.WriteTransaction() ?? throw new InvalidOperationException("Failed to open transaction."))
// Keep the transaction open as short as possible.
using (var transaction = doc.WriteTransaction())
{
array.InsertRange(transaction, array.Length, notifications.Select(x => x.ToInput()).ToArray());
}
Expand Down
2 changes: 1 addition & 1 deletion Demo/Client/src/components/Awareness.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ export const Awareness = () => {
}, [awareness]);

return (
<Input type='textarea' value={JSON.stringify(state, undefined, 2)} />
<Input type='textarea' readOnly value={JSON.stringify(state, undefined, 2)} />
);
};
2 changes: 1 addition & 1 deletion Demo/Client/src/components/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const Chat = ({ isReadonly }: { isReadonly: boolean }) => {
</div>

{!isReadonly &&
<Row noGutters>
<Row className='gap-0'>
<Col>
<Input value={text} onChange={_setText}></Input>
</Col>
Expand Down
4 changes: 2 additions & 2 deletions Demo/Client/src/components/Increment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const Increment = () => {

React.useEffect(() => {
const handler = () => {
setState(map.get('value') || 0);
setState(map.get('value') as number || 0);
};

handler();
Expand All @@ -35,7 +35,7 @@ export const Increment = () => {
};

return (
<Row noGutters>
<Row className='gap-0'>
<Col xs='auto'>
<Button onClick={_decrement}>-1</Button>
</Col>
Expand Down
4 changes: 1 addition & 3 deletions Demo/Client/src/components/YjsMonacoEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ export const YjsMonacoEditor = () => {

return (
<div>
<MonacoEditor
editorDidMount={(e) => _onEditorDidMount(e)}
/>
<MonacoEditor editorDidMount={(e) => _onEditorDidMount(e)} />
</div>
);
};
1 change: 1 addition & 0 deletions Demo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public static void Main(string[] args)

var yDotNet =
builder.Services.AddYDotNet()
.AutoCleanup()
.AddCallback<Callback>()
.AddWebSockets();

Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Version>0.2.13</Version>
<Version>0.3.0</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
Expand Down
2 changes: 1 addition & 1 deletion YDotNet.Server.MongoDB/MongoDocumentStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ await collection.Indexes.CreateOneAsync(
Builders<DocumentEntity>.IndexKeys.Ascending(x => x.Expiration),
new CreateIndexOptions
{
ExpireAfter = TimeSpan.Zero
ExpireAfter = TimeSpan.Zero,
}),
cancellationToken: cancellationToken).ConfigureAwait(false);
}
Expand Down
4 changes: 2 additions & 2 deletions YDotNet.Server.WebSockets/WebSocketDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public WebSocketDecoder(WebSocket webSocket)
this.webSocket = webSocket;
}

public bool CanRead { get; set; } = true;
public bool CanRead { get; private set; } = true;

public bool HasMore => bufferIndex < bufferLength;

Expand Down Expand Up @@ -61,7 +61,7 @@ private async Task ReadIfEndOfBufferReachedAsync(CancellationToken ct)
if (received.CloseStatus != null)
{
CanRead = false;
throw new InvalidOperationException("Socket is already closed.");
throw new WebSocketException("Socket is already closed.");
}

bufferLength = received.Count;
Expand Down
8 changes: 8 additions & 0 deletions YDotNet.Server/CleanupOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace YDotNet.Server;

public sealed class CleanupOptions
{
public TimeSpan Interval { get; set; } = TimeSpan.FromMinutes(1);

public TimeSpan LogWaitTime { get; set; } = TimeSpan.FromMinutes(10);
}
54 changes: 54 additions & 0 deletions YDotNet.Server/DefaultDocumentCleaner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace YDotNet.Server;

internal class DefaultDocumentCleaner : BackgroundService
{
private readonly IDocumentManager documentManager;
private readonly ILogger<DefaultDocumentCleaner> logger;
private readonly CleanupOptions options;
private DateTime lastLogging;

public Func<DateTime> Clock { get; } = () => DateTime.UtcNow;

public DefaultDocumentCleaner(IDocumentManager documentManager, IOptions<CleanupOptions> options, ILogger<DefaultDocumentCleaner> logger)
{
this.documentManager = documentManager;
this.logger = logger;
this.options = options.Value;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var timer = new PeriodicTimer(options.Interval);

while (await timer.WaitForNextTickAsync(stoppingToken).ConfigureAwait(false))
{
try
{
await documentManager.CleanupAsync(stoppingToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// This is an expected exception when the process stops. There is no good reason to handle that.
}
catch (Exception ex)
{
var now = Clock();

var timeSinceLastLogging = now - lastLogging;
// We run this loop very often. If there is an exception, it could flood the log with duplicate log entries.
// Therefore use a wait time between two log calls.
if (timeSinceLastLogging < options.LogWaitTime)
{
return;
}

logger.LogError(ex, "Failed to cleanup document manager.");
lastLogging = now;
}
}
}
}
10 changes: 7 additions & 3 deletions YDotNet.Server/DefaultDocumentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public async ValueTask<UpdateResult> ApplyUpdateAsync(DocumentContext context, b
{
var result = new UpdateResult
{
Diff = stateDiff
Diff = stateDiff,
};
using (var transaction = doc.WriteTransactionOrThrow())
Expand Down Expand Up @@ -150,6 +150,7 @@ await callback.OnClientDisconnectedAsync(new ClientDisconnectedEvent
{
Context = context,
Source = this,
Reason = DisconnectReason.Disconnect,
}).ConfigureAwait(false);
}
}
Expand All @@ -163,16 +164,19 @@ await callback.OnClientDisconnectedAsync(new ClientDisconnectedEvent
{
Context = new DocumentContext(documentName, clientId),
Source = this,
Reason = DisconnectReason.Cleanup,
}).ConfigureAwait(false);
}

cache.RemoveEvictedItems();
}

public ValueTask<IReadOnlyDictionary<ulong, ConnectedUser>> GetAwarenessAsync(
public async ValueTask<IReadOnlyDictionary<ulong, ConnectedUser>> GetAwarenessAsync(
DocumentContext context,
CancellationToken ct = default)
{
return new ValueTask<IReadOnlyDictionary<ulong, ConnectedUser>>(users.GetUsers(context.DocumentName));
await CleanupAsync(default).ConfigureAwait(false);

return users.GetUsers(context.DocumentName);
}
}
7 changes: 7 additions & 0 deletions YDotNet.Server/Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public sealed class DocumentChangedEvent : DocumentChangeEvent

public sealed class ClientDisconnectedEvent : DocumentEvent
{
required public DisconnectReason Reason { get; init; }
}

public sealed class ClientAwarenessEvent : DocumentEvent
Expand All @@ -38,3 +39,9 @@ public sealed class ClientAwarenessEvent : DocumentEvent

required public ulong ClientClock { get; set; }
}

public enum DisconnectReason
{
Disconnect,
Cleanup,
}
9 changes: 8 additions & 1 deletion YDotNet.Server/ServiceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static YDotnetRegistration AddYDotNet(this IServiceCollection services)

return new YDotnetRegistration
{
Services = services
Services = services,
};
}

Expand All @@ -28,6 +28,13 @@ public static YDotnetRegistration AddCallback<T>(this YDotnetRegistration regist
registration.Services.AddSingleton<IDocumentCallback, T>();
return registration;
}

public static YDotnetRegistration AutoCleanup(this YDotnetRegistration registration, Action<CleanupOptions>? configure = null)
{
registration.Services.Configure(configure ?? (x => { }));
registration.Services.AddSingleton<IHostedService, DefaultDocumentCleaner>();
return registration;
}
}

#pragma warning disable MA0048 // File name must match type name
Expand Down
2 changes: 1 addition & 1 deletion YDotNet/Document/Cells/OutputTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,5 @@ public enum OutputTag
/// <summary>
/// Represents a cell with an <see cref="YDotNet.Document.Doc" /> value.
/// </summary>
Doc = 7
Doc = 7,
}
2 changes: 1 addition & 1 deletion YDotNet/Document/Options/DocEncoding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ public enum DocEncoding
/// <summary>
/// Compute editable strings length and offset using UTF-32 (Unicode) code points number.
/// </summary>
Utf32 = 2
Utf32 = 2,
}
2 changes: 1 addition & 1 deletion YDotNet/Document/Options/DocOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ internal DocOptionsNative ToNative()
Encoding = (byte)Encoding,
SkipGc = (byte)(SkipGarbageCollection ? 1 : 0),
AutoLoad = (byte)(AutoLoad ? 1 : 0),
ShouldLoad = (byte)(ShouldLoad ? 1 : 0)
ShouldLoad = (byte)(ShouldLoad ? 1 : 0),
};
}
}
2 changes: 1 addition & 1 deletion YDotNet/Document/StickyIndexes/StickyAssociationType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ public enum StickyAssociationType : sbyte
/// <summary>
/// The corresponding <see cref="StickyIndex" /> points to space before the referenced element.
/// </summary>
Before = -1
Before = -1,
}
2 changes: 1 addition & 1 deletion YDotNet/Document/Transactions/TransactionUpdateResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ public enum TransactionUpdateResult
/// <summary>
/// Failure when trying to decode JSON content.
/// </summary>
Other = 5
Other = 5,
}
2 changes: 1 addition & 1 deletion YDotNet/Document/Types/Events/EventBranchTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ public enum EventBranchTag : sbyte
/// <summary>
/// This event holds an <see cref="XmlTextEvent" /> instance.
/// </summary>
XmlText = 5
XmlText = 5,
}
2 changes: 1 addition & 1 deletion YDotNet/Document/Types/Events/EventChangeTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ public enum EventChangeTag
/// <summary>
/// Represents the update of content.
/// </summary>
Retain
Retain,
}
2 changes: 1 addition & 1 deletion YDotNet/Document/Types/Events/EventDeltaTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ public enum EventDeltaTag
/// <summary>
/// Represents the update of content.
/// </summary>
Retain
Retain,
}
2 changes: 1 addition & 1 deletion YDotNet/Document/Types/Events/EventKeyChangeTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ public enum EventKeyChangeTag
/// <summary>
/// Represents that the value under this key was updated.
/// </summary>
Update
Update,
}
2 changes: 1 addition & 1 deletion YDotNet/Document/Types/Events/EventPathSegmentTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ public enum EventPathSegmentTag : sbyte
/// <summary>
/// The <see cref="EventPathSegment" /> contains an <see cref="int" /> value.
/// </summary>
Index = 2
Index = 2,
}
2 changes: 1 addition & 1 deletion YDotNet/Document/UndoManagers/Events/UndoEventKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ public enum UndoEventKind
/// <summary>
/// Represents a redo operation.
/// </summary>
Redo = 1
Redo = 1,
}
2 changes: 1 addition & 1 deletion YDotNet/Document/UndoManagers/UndoManagerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal UndoManagerOptionsNative ToNative()
{
return new UndoManagerOptionsNative
{
CaptureTimeoutMilliseconds = CaptureTimeoutMilliseconds
CaptureTimeoutMilliseconds = CaptureTimeoutMilliseconds,
};
}
}
2 changes: 1 addition & 1 deletion YDotNet/Native/Types/Branches/BranchKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ internal enum BranchKind
Map = 2,
Text = 3,
XmlElement = 4,
XmlText = 5
XmlText = 5,
}
2 changes: 1 addition & 1 deletion YDotNet/Native/Types/Events/EventChangeTagNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ internal enum EventChangeTagNative : sbyte
{
Add = 1,
Remove = 2,
Retain = 3
Retain = 3,
}
2 changes: 1 addition & 1 deletion YDotNet/Native/Types/Events/EventDeltaTagNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ internal enum EventDeltaTagNative : sbyte
{
Add = 1,
Remove = 2,
Retain = 3
Retain = 3,
}
2 changes: 1 addition & 1 deletion YDotNet/Native/Types/Events/EventKeyChangeTagNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ internal enum EventKeyChangeTagNative : byte
{
Add = 4,
Remove = 5,
Update = 6
Update = 6,
}
2 changes: 1 addition & 1 deletion YDotNet/Native/UndoManager/Events/UndoEventKindNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ namespace YDotNet.Native.UndoManager.Events;
internal enum UndoEventKindNative : byte
{
Undo = 0,
Redo = 1
Redo = 1,
}

0 comments on commit 488714b

Please sign in to comment.