Skip to content

Introduce ITransactionCallback, replacement for INodeSessionCallback #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,22 @@ Func<IServiceProvider, INodeSessionCallback> buildSessionCallback
buildSessionCallback: buildSessionCallback ?? throw new ArgumentNullException(nameof(buildSessionCallback))
);

#pragma warning disable CS0419
/// <remarks>
/// If <see cref="UsePluginProvider"/> is called, the configurations made by this method will be overridden.
/// </remarks>
public static IMuninNodeBuilder UseTransactionCallback(
this IMuninNodeBuilder builder,
Func<CancellationToken, ValueTask>? onStartTransactionAsyncFunc,
Func<CancellationToken, ValueTask>? onEndTransactionAsyncFunc
)
#pragma warning restore CS0419
=> MuninNodeBuilderExtensions.UseTransactionCallback(
builder: ThrowIfBuilderTypeIsNotSupported(builder ?? throw new ArgumentNullException(nameof(builder))),
onStartTransactionAsyncFunc: onStartTransactionAsyncFunc,
onEndTransactionAsyncFunc: onEndTransactionAsyncFunc
);

public static IMuninNodeBuilder UseListenerFactory(
this IMuninNodeBuilder builder,
IMuninNodeListenerFactory listenerFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand All @@ -23,7 +25,9 @@ public class MuninNodeBuilder : IMuninNodeBuilder {
#pragma warning restore CS0618
private readonly List<Func<IServiceProvider, IPlugin>> pluginFactories = new(capacity: 4);
private Func<IServiceProvider, IPluginProvider>? buildPluginProvider;
private Func<IServiceProvider, INodeSessionCallback>? buildSessionCallback;
[Obsolete] private Func<IServiceProvider, INodeSessionCallback>? buildSessionCallback;
private Func<CancellationToken, ValueTask>? onStartTransactionAsyncFunc;
private Func<CancellationToken, ValueTask>? onEndTransactionAsyncFunc;
private Func<IServiceProvider, IMuninNodeListenerFactory>? buildListenerFactory;

/// <summary>
Expand Down Expand Up @@ -64,6 +68,7 @@ Func<IServiceProvider, IPluginProvider> buildPluginProvider
this.buildPluginProvider = buildPluginProvider;
}

[Obsolete]
internal void SetSessionCallbackFactory(
Func<IServiceProvider, INodeSessionCallback> buildSessionCallback
)
Expand All @@ -74,6 +79,15 @@ Func<IServiceProvider, INodeSessionCallback> buildSessionCallback
this.buildSessionCallback = buildSessionCallback;
}

internal void SetTransactionCallback(
Func<CancellationToken, ValueTask>? onStartTransactionAsyncFunc,
Func<CancellationToken, ValueTask>? onEndTransactionAsyncFunc
)
{
this.onStartTransactionAsyncFunc = onStartTransactionAsyncFunc;
this.onEndTransactionAsyncFunc = onEndTransactionAsyncFunc;
}

internal void SetListenerFactory(
Func<IServiceProvider, IMuninNodeListenerFactory> buildListenerFactory
)
Expand All @@ -100,26 +114,51 @@ public IMuninNode Build(IServiceProvider serviceProvider)
pluginProvider: buildPluginProvider is null
? new PluginProvider(
plugins: pluginFactories.Select(factory => factory(serviceProvider)).ToList(),
sessionCallback: buildSessionCallback?.Invoke(serviceProvider)
#pragma warning disable CS0612
sessionCallback: buildSessionCallback?.Invoke(serviceProvider),
#pragma warning restore CS0612
onStartTransactionAsyncFunc: onStartTransactionAsyncFunc,
onEndTransactionAsyncFunc: onEndTransactionAsyncFunc
)
: buildPluginProvider.Invoke(serviceProvider),
listenerFactory: buildListenerFactory?.Invoke(serviceProvider),
serviceProvider: serviceProvider
);
}

private sealed class PluginProvider : IPluginProvider {
private sealed class PluginProvider : IPluginProvider, ITransactionCallback {
public IReadOnlyCollection<IPlugin> Plugins { get; }

[Obsolete]
public INodeSessionCallback? SessionCallback { get; }

private readonly Func<CancellationToken, ValueTask>? onStartTransactionAsyncFunc;
private readonly Func<CancellationToken, ValueTask>? onEndTransactionAsyncFunc;

public PluginProvider(
IReadOnlyCollection<IPlugin> plugins,
INodeSessionCallback? sessionCallback
#pragma warning disable CS0618
INodeSessionCallback? sessionCallback,
#pragma warning restore CS0618
Func<CancellationToken, ValueTask>? onStartTransactionAsyncFunc,
Func<CancellationToken, ValueTask>? onEndTransactionAsyncFunc
)
{
Plugins = plugins ?? throw new ArgumentNullException(nameof(plugins));

#pragma warning disable CS0612
SessionCallback = sessionCallback;
#pragma warning restore CS0612

this.onStartTransactionAsyncFunc = onStartTransactionAsyncFunc;
this.onEndTransactionAsyncFunc = onEndTransactionAsyncFunc;
}

public ValueTask StartTransactionAsync(CancellationToken cancellationToken)
=> onStartTransactionAsyncFunc?.Invoke(cancellationToken) ?? default;

public ValueTask EndTransactionAsync(CancellationToken cancellationToken)
=> onEndTransactionAsyncFunc?.Invoke(cancellationToken) ?? default;
}

protected virtual IMuninNode Build(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,17 @@ Func<IServiceProvider, IPluginProvider> buildPluginProvider
return builder;
}

#pragma warning disable CS0618
private const string ObsoleteMessageForUseSessionCallback =
$"{nameof(INodeSessionCallback)} is deprecated and will be removed in the next major version release. " +
$"Use ${nameof(UseTransactionCallback)} instead of {nameof(UseSessionCallback)}.";
#pragma warning restore CS0618

#pragma warning disable CS0419
/// <remarks>
/// If <see cref="UsePluginProvider"/> is called, the configurations made by this method will be overridden.
/// </remarks>
[Obsolete(ObsoleteMessageForUseSessionCallback)]
public static TMuninNodeBuilder UseSessionCallback<TMuninNodeBuilder>(
this TMuninNodeBuilder builder,
INodeSessionCallback sessionCallback
Expand All @@ -126,6 +133,7 @@ INodeSessionCallback sessionCallback
/// <remarks>
/// If <see cref="UsePluginProvider"/> is called, the configurations made by this method will be overridden.
/// </remarks>
[Obsolete(ObsoleteMessageForUseSessionCallback)]
public static TMuninNodeBuilder UseSessionCallback<TMuninNodeBuilder>(
this TMuninNodeBuilder builder,
Func<string, CancellationToken, ValueTask>? reportSessionStartedAsyncFunc,
Expand All @@ -141,6 +149,7 @@ public static TMuninNodeBuilder UseSessionCallback<TMuninNodeBuilder>(
)
);

[Obsolete]
private sealed class SessionCallbackFuncWrapper(
Func<string, CancellationToken, ValueTask>? reportSessionStartedAsyncFunc,
Func<string, CancellationToken, ValueTask>? reportSessionClosedAsyncFunc
Expand All @@ -160,6 +169,7 @@ public ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken ca
/// <remarks>
/// If <see cref="UsePluginProvider"/> is called, the configurations made by this method will be overridden.
/// </remarks>
[Obsolete(ObsoleteMessageForUseSessionCallback)]
public static TMuninNodeBuilder UseSessionCallback<TMuninNodeBuilder>(
this TMuninNodeBuilder builder,
Func<IServiceProvider, INodeSessionCallback> buildSessionCallback
Expand All @@ -177,6 +187,29 @@ Func<IServiceProvider, INodeSessionCallback> buildSessionCallback
return builder;
}

#pragma warning disable CS0419
/// <remarks>
/// If <see cref="UsePluginProvider"/> is called, the configurations made by this method will be overridden.
/// </remarks>
public static TMuninNodeBuilder UseTransactionCallback<TMuninNodeBuilder>(
this TMuninNodeBuilder builder,
Func<CancellationToken, ValueTask>? onStartTransactionAsyncFunc,
Func<CancellationToken, ValueTask>? onEndTransactionAsyncFunc
)
where TMuninNodeBuilder : MuninNodeBuilder
#pragma warning restore CS0419
{
if (builder is null)
throw new ArgumentNullException(nameof(builder));

builder.SetTransactionCallback(
onStartTransactionAsyncFunc,
onEndTransactionAsyncFunc
);

return builder;
}

public static TMuninNodeBuilder UseListenerFactory<TMuninNodeBuilder>(
this TMuninNodeBuilder builder,
IMuninNodeListenerFactory listenerFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,28 @@ public ValueTask HandleTransactionStartAsync(
}

/// <remarks>
/// In the default implementation, a banner response is sent back to the client.
/// In the default implementation, <see cref="ITransactionCallback.StartTransactionAsync"/> is
/// called for <see cref="IPluginProvider"/> and <see cref="IPlugin"/>s,
/// and then a banner response is sent back to the client.
/// </remarks>
protected virtual ValueTask HandleTransactionStartAsyncCore(
protected virtual async ValueTask HandleTransactionStartAsyncCore(
IMuninNodeClient client,
CancellationToken cancellationToken
)
=> SendResponseAsync(
{
if (profile.PluginProvider is ITransactionCallback providerTransactionCallback)
await providerTransactionCallback.StartTransactionAsync(cancellationToken).ConfigureAwait(false);

foreach (var pluginTransactionCallback in plugins.Values.OfType<ITransactionCallback>()) {
await pluginTransactionCallback.StartTransactionAsync(cancellationToken).ConfigureAwait(false);
}

await SendResponseAsync(
client,
banner,
cancellationToken
);
).ConfigureAwait(false);
}

/// <inheritdoc cref="IMuninProtocolHandler.HandleTransactionEndAsync"/>
public ValueTask HandleTransactionEndAsync(
Expand All @@ -178,11 +189,22 @@ public ValueTask HandleTransactionEndAsync(
return HandleTransactionEndAsyncCore(client, cancellationToken);
}

protected virtual ValueTask HandleTransactionEndAsyncCore(
/// <remarks>
/// In the default implementation, <see cref="ITransactionCallback.EndTransactionAsync"/> is
/// called for <see cref="IPluginProvider"/> and <see cref="IPlugin"/>s.
/// </remarks>
protected virtual async ValueTask HandleTransactionEndAsyncCore(
IMuninNodeClient client,
CancellationToken cancellationToken
)
=> default; // do nothing in this class
{
foreach (var pluginTransactionCallback in plugins.Values.OfType<ITransactionCallback>()) {
await pluginTransactionCallback.EndTransactionAsync(cancellationToken).ConfigureAwait(false);
}

if (profile.PluginProvider is ITransactionCallback providerTransactionCallback)
await providerTransactionCallback.EndTransactionAsync(cancellationToken).ConfigureAwait(false);
}

private static bool ExpectCommand(
ReadOnlySequence<byte> commandLine,
Expand Down
2 changes: 2 additions & 0 deletions src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.Create.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ partial class LocalNode {
#pragma warning restore IDE0040
private class ReadOnlyCollectionPluginProvider : IPluginProvider {
public IReadOnlyCollection<IPlugin> Plugins { get; }

[Obsolete]
public INodeSessionCallback? SessionCallback => null;

public ReadOnlyCollectionPluginProvider(IReadOnlyCollection<IPlugin> plugins)
Expand Down
8 changes: 7 additions & 1 deletion src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/NodeBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -562,13 +562,14 @@ CancellationToken cancellationToken
LogSessionStarted(Logger, null);

try {
// TODO: rename INodeSessionCallback to ITransactionCallback
#pragma warning disable CS0612,CS0618
if (PluginProvider.SessionCallback is INodeSessionCallback pluginProviderSessionCallback)
await pluginProviderSessionCallback.ReportSessionStartedAsync(sessionId, cancellationToken).ConfigureAwait(false);

foreach (var pluginSessionCallback in EnumerateSessionCallbackForPlugins(PluginProvider)) {
await pluginSessionCallback.ReportSessionStartedAsync(sessionId, cancellationToken).ConfigureAwait(false);
}
#pragma warning restore CS0612,CS0618

// https://docs.microsoft.com/ja-jp/dotnet/standard/io/pipelines
var pipe = new Pipe();
Expand All @@ -582,17 +583,22 @@ await Task.WhenAll(
LogSessionClosed(Logger, null);
}
finally {
#pragma warning disable CS0612,CS0618
foreach (var pluginSessionCallback in EnumerateSessionCallbackForPlugins(PluginProvider)) {
await pluginSessionCallback.ReportSessionClosedAsync(sessionId, cancellationToken).ConfigureAwait(false);
}

if (PluginProvider.SessionCallback is INodeSessionCallback pluginProviderSessionCallback)
await pluginProviderSessionCallback.ReportSessionClosedAsync(sessionId, cancellationToken).ConfigureAwait(false);
#pragma warning restore CS0612,CS0618

await protocolHandler.HandleTransactionEndAsync(client, cancellationToken).ConfigureAwait(false);
}

[Obsolete]
#pragma warning disable CS0618
static IEnumerable<INodeSessionCallback> EnumerateSessionCallbackForPlugins(IPluginProvider pluginProvider)
#pragma warning restore CS0618
{
foreach (var plugin in pluginProvider.EnumeratePlugins(flattenMultigraphPlugins: true)) {
if (plugin.SessionCallback is INodeSessionCallback pluginSessionCallback)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@ namespace Smdn.Net.MuninPlugin;
#pragma warning disable IDE0055
public sealed class AggregatePluginProvider :
ReadOnlyCollection<IPluginProvider>,
#pragma warning disable CS0618
INodeSessionCallback,
IPluginProvider
#pragma warning restore CS0618
IPluginProvider,
ITransactionCallback
{
#pragma warning restore IDE0055
/*
* IPluginProvider
*/
public IReadOnlyCollection<IPlugin> Plugins { get; }

[Obsolete]
INodeSessionCallback? IPluginProvider.SessionCallback => this;

/*
Expand All @@ -37,6 +41,7 @@ public AggregatePluginProvider(IList<IPluginProvider> pluginProviders)
Plugins = Items.SelectMany(static provider => provider.Plugins).ToList();
}

#pragma warning disable CS0618
/*
* INodeSessionCallback
*/
Expand All @@ -59,4 +64,26 @@ async ValueTask INodeSessionCallback.ReportSessionClosedAsync(string sessionId,
await sessionCallback.ReportSessionClosedAsync(sessionId, cancellationToken).ConfigureAwait(false);
}
}
#pragma warning restore CS0618

/*
* ITransactionCallback
*/
async ValueTask ITransactionCallback.StartTransactionAsync(CancellationToken cancellationToken)
{
foreach (var transactionCallback in Items.OfType<ITransactionCallback>()) {
cancellationToken.ThrowIfCancellationRequested();

await transactionCallback.StartTransactionAsync(cancellationToken).ConfigureAwait(false);
}
}

async ValueTask ITransactionCallback.EndTransactionAsync(CancellationToken cancellationToken)
{
foreach (var transactionCallback in Items.OfType<ITransactionCallback>()) {
cancellationToken.ThrowIfCancellationRequested();

await transactionCallback.EndTransactionAsync(cancellationToken).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
// SPDX-License-Identifier: MIT

using System;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -9,6 +10,7 @@ namespace Smdn.Net.MuninPlugin;
/// <summary>
/// Defines the callbacks when a request session from the <c>munin-update</c> starts or ends.
/// </summary>
[Obsolete(message: ObsoleteMessage.TypeReference)]
public interface INodeSessionCallback {
/// <summary>
/// Implements a callback to be called when <c>munin-update</c> starts a session.
Expand All @@ -25,4 +27,15 @@ public interface INodeSessionCallback {
/// <param name="sessionId">A unique ID that <see cref="MuninNode.NodeBase"/> associates with the session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param>
ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken);

internal static class ObsoleteMessage {
public const string TypeReference =
$"{nameof(INodeSessionCallback)} is deprecated and will be removed in the next major version release. " +
$"Use ${nameof(ITransactionCallback)} interface instead.";

public const string SessionCallbackProperty =
$"{nameof(INodeSessionCallback)} is deprecated and will be removed in the next major version release. " +
$"Instead of setting an object that implements ${nameof(INodeSessionCallback)} to the property, " +
$"implement the ${nameof(ITransactionCallback)} interface to the type itself.";
}
}
Loading
Loading