aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-01-21 21:45:05 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-01-21 21:45:05 -0500
commit5c2dc879179e4d8ece012a5078152a601a4914ba (patch)
tree3b61b171a6967c4e935b3428a517454f049758f3
parent0e38da4b583f0227beca3a3f870c395f152c507a (diff)
Serialzer pooling support added
-rw-r--r--lib/VNLib.Data.Caching/src/ClientExtensions.cs60
-rw-r--r--lib/VNLib.Data.Caching/src/ReusableJsonWriter.cs69
2 files changed, 110 insertions, 19 deletions
diff --git a/lib/VNLib.Data.Caching/src/ClientExtensions.cs b/lib/VNLib.Data.Caching/src/ClientExtensions.cs
index a207886..0a24a83 100644
--- a/lib/VNLib.Data.Caching/src/ClientExtensions.cs
+++ b/lib/VNLib.Data.Caching/src/ClientExtensions.cs
@@ -23,7 +23,6 @@
*/
using System;
-using System.IO;
using System.Linq;
using System.Buffers;
using System.Text.Json;
@@ -31,23 +30,26 @@ using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Text.Json.Serialization;
+using System.Runtime.CompilerServices;
using VNLib.Utils.Logging;
+using VNLib.Utils.Memory.Caching;
using VNLib.Net.Messaging.FBM;
using VNLib.Net.Messaging.FBM.Client;
using VNLib.Net.Messaging.FBM.Server;
using VNLib.Data.Caching.Exceptions;
-
using static VNLib.Data.Caching.Constants;
namespace VNLib.Data.Caching
{
-
/// <summary>
/// Provides caching extension methods for <see cref="FBMClient"/>
/// </summary>
public static class ClientExtensions
{
+ //Create threadlocal writer for attempted reuse without locks
+ private static readonly ObjectRental<ReusableJsonWriter> JsonWriterPool = ObjectRental.Create<ReusableJsonWriter>();
+
private static readonly JsonSerializerOptions LocalOptions = new()
{
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
@@ -63,12 +65,10 @@ namespace VNLib.Data.Caching
DefaultBufferSize = 128
};
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void LogDebug(this FBMClient client, string message, params object?[] args)
{
- if (client.Config.DebugLog != null)
- {
- client.Config.DebugLog.Debug($"[CACHE] : {message}", args);
- }
+ client.Config.DebugLog?.Debug($"[CACHE] : {message}", args);
}
/// <summary>
@@ -96,24 +96,29 @@ namespace VNLib.Data.Caching
{
//Set action as get/create
request.WriteHeader(HeaderCommand.Action, Actions.Get);
- //Set session-id header
+
+ //Set object id header
request.WriteHeader(Constants.ObjectId, objectId);
//Make request
using FBMResponse response = await client.SendAsync(request, cancellationToken);
-
response.ThrowIfNotSet();
+
//Get the status code
ReadOnlyMemory<char> status = response.Headers.FirstOrDefault(static a => a.Key == HeaderCommand.Status).Value;
+
+ //Check ok status code, then its safe to deserialize
if (status.Span.Equals(ResponseCodes.Okay, StringComparison.Ordinal))
{
return JsonSerializer.Deserialize<T>(response.ResponseBody, LocalOptions);
}
- //Session may not exist on the server yet
+
+ //Object may not exist on the server yet
if (status.Span.Equals(ResponseCodes.NotFound, StringComparison.Ordinal))
{
return default;
}
+
throw new InvalidStatusException("Invalid status code recived for object get request", status.ToString());
}
finally
@@ -145,34 +150,39 @@ namespace VNLib.Data.Caching
_ = client ?? throw new ArgumentNullException(nameof(client));
client.LogDebug("Updating object {id}, newid {nid}", objectId, newId);
-
+
+ //Rent new json writer
+ ReusableJsonWriter writer = JsonWriterPool.Rent();
+
//Rent a new request
FBMRequest request = client.RentRequest();
try
{
//Set action as get/create
request.WriteHeader(HeaderCommand.Action, Actions.AddOrUpdate);
+
//Set session-id header
request.WriteHeader(Constants.ObjectId, objectId);
+
//if new-id set, set the new-id header
if (!string.IsNullOrWhiteSpace(newId))
{
request.WriteHeader(Constants.NewObjectId, newId);
}
+
//Get the body writer for the message
IBufferWriter<byte> bodyWriter = request.GetBodyWriter();
- //Write json data to the message
- using (Utf8JsonWriter jsonWriter = new(bodyWriter))
- {
- JsonSerializer.Serialize(jsonWriter, data, LocalOptions);
- }
+
+ //Serialize the message
+ writer.Serialize(bodyWriter, data, LocalOptions);
//Make request
using FBMResponse response = await client.SendAsync(request, cancellationToken);
-
response.ThrowIfNotSet();
+
//Get the status code
ReadOnlyMemory<char> status = response.Headers.FirstOrDefault(static a => a.Key == HeaderCommand.Status).Value;
+
//Check status code
if (status.Span.Equals(ResponseCodes.Okay, StringComparison.OrdinalIgnoreCase))
{
@@ -182,6 +192,7 @@ namespace VNLib.Data.Caching
{
throw new ObjectNotFoundException($"object {objectId} not found on remote server");
}
+
//Invalid status
throw new InvalidStatusException("Invalid status code recived for object upsert request", status.ToString());
}
@@ -189,6 +200,8 @@ namespace VNLib.Data.Caching
{
//Return the request(clears data and reset)
client.ReturnRequest(request);
+ //Return writer to pool later
+ JsonWriterPool.Return(writer);
}
}
@@ -208,6 +221,7 @@ namespace VNLib.Data.Caching
_ = client ?? throw new ArgumentNullException(nameof(client));
client.LogDebug("Deleting object {id}", objectId);
+
//Rent a new request
FBMRequest request = client.RentRequest();
try
@@ -219,10 +233,11 @@ namespace VNLib.Data.Caching
//Make request
using FBMResponse response = await client.SendAsync(request, cancellationToken);
-
response.ThrowIfNotSet();
+
//Get the status code
ReadOnlyMemory<char> status = response.Headers.FirstOrDefault(static a => a.Key == HeaderCommand.Status).Value;
+
if (status.Span.Equals(ResponseCodes.Okay, StringComparison.Ordinal))
{
return;
@@ -231,6 +246,7 @@ namespace VNLib.Data.Caching
{
throw new ObjectNotFoundException($"object {objectId} not found on remote server");
}
+
throw new InvalidStatusException("Invalid status code recived for object get request", status.ToString());
}
finally
@@ -278,39 +294,45 @@ namespace VNLib.Data.Caching
/// <param name="context"></param>
/// <returns>The id of the object requested</returns>
/// <exception cref="InvalidOperationException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string ObjectId(this FBMContext context)
{
return context.Request.Headers.First(static kvp => kvp.Key == Constants.ObjectId).Value.ToString();
}
+
/// <summary>
/// Gets the new ID of the object if specified from the request. Null if the request did not specify an id update
/// </summary>
/// <param name="context"></param>
/// <returns>The new ID of the object if speicifed, null otherwise</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string? NewObjectId(this FBMContext context)
{
return context.Request.Headers.FirstOrDefault(static kvp => kvp.Key == Constants.NewObjectId).Value.ToString();
}
+
/// <summary>
/// Gets the request method for the request
/// </summary>
/// <param name="context"></param>
/// <returns>The request method string</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string Method(this FBMContext context)
{
return context.Request.Headers.First(static kvp => kvp.Key == HeaderCommand.Action).Value.ToString();
}
+
/// <summary>
/// Closes a response with a status code
/// </summary>
/// <param name="context"></param>
/// <param name="responseCode">The status code to send to the client</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CloseResponse(this FBMContext context, string responseCode)
{
context.Response.WriteHeader(HeaderCommand.Status, responseCode);
}
-
/// <summary>
/// Initializes the worker for a reconnect policy and returns an object that can listen for changes
/// and configure the connection as necessary
diff --git a/lib/VNLib.Data.Caching/src/ReusableJsonWriter.cs b/lib/VNLib.Data.Caching/src/ReusableJsonWriter.cs
new file mode 100644
index 0000000..c763f91
--- /dev/null
+++ b/lib/VNLib.Data.Caching/src/ReusableJsonWriter.cs
@@ -0,0 +1,69 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching
+* File: ReusableJsonWriter.cs
+*
+* ReusableJsonWriter.cs is part of VNLib.Data.Caching which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Data.Caching is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.IO;
+using System.Buffers;
+using System.Text.Json;
+
+using VNLib.Utils;
+
+namespace VNLib.Data.Caching
+{
+ internal sealed class ReusableJsonWriter : VnDisposeable
+ {
+ private readonly Utf8JsonWriter _writer;
+
+ public ReusableJsonWriter()
+ {
+ _writer = new(Stream.Null);
+ }
+
+ /// <summary>
+ /// Serializes the message and writes the serialzied data to the buffer writer
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="writer">The buffer writer to store data at</param>
+ /// <param name="value">The object to serialize</param>
+ /// <param name="options">Optional - serializer options</param>
+ public void Serialize<T>(IBufferWriter<byte> writer, T value, JsonSerializerOptions? options = null)
+ {
+ //Init the writer with the new buffer writer
+ _writer.Reset(writer);
+ try
+ {
+ //Serialize message
+ JsonSerializer.Serialize(_writer, value, options);
+ //Flush writer to underlying buffer
+ _writer.Flush();
+ }
+ finally
+ {
+ //Unlink the writer
+ _writer.Reset(Stream.Null);
+ }
+ }
+
+ protected override void Free() => _writer.Dispose();
+ }
+}