diff --git a/csharp/sdk/CppDynamicLibrary/CppDynamicLibrary.csproj b/csharp/sdk/CppDynamicLibrary/CppDynamicLibrary.csproj new file mode 100644 index 00000000..d9f2cb2b --- /dev/null +++ b/csharp/sdk/CppDynamicLibrary/CppDynamicLibrary.csproj @@ -0,0 +1,27 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + + + + + + + + Always + + + + diff --git a/csharp/sdk/CppDynamicLibrary/Program.cs b/csharp/sdk/CppDynamicLibrary/Program.cs new file mode 100644 index 00000000..b1e519da --- /dev/null +++ b/csharp/sdk/CppDynamicLibrary/Program.cs @@ -0,0 +1,175 @@ +// This file is part of the ArmoniK project +// +// Copyright (C) ANEO, 2021-2026. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License") +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Text; + +using ArmoniK.Extensions.CSharp.Client; +using ArmoniK.Extensions.CSharp.Client.Common; +using ArmoniK.Extensions.CSharp.Client.Common.Domain.Blob; +using ArmoniK.Extensions.CSharp.Client.Common.Domain.Task; +using ArmoniK.Extensions.CSharp.Client.Handles; +using ArmoniK.Extensions.CSharp.Common.Common.Domain.Task; +using ArmoniK.Extensions.CSharp.Common.Library; + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +using Serilog; +using Serilog.Extensions.Logging; +// ReSharper disable ArrangeNamespaceBody + +namespace CppDynamicLibrary; + +internal class Callback : ICallback +{ + public byte[] Result { get; private set; } = []; + + public ValueTask OnSuccessAsync(BlobHandle blob, + byte[] rawData, + CancellationToken cancellationToken) + { + Result = rawData; + return ValueTask.CompletedTask; + } + + public ValueTask OnErrorAsync(BlobHandle blob, + Exception? exception, + CancellationToken cancellationToken) + { + Console.WriteLine(exception?.Message ?? $"blob {blob.BlobInfo.BlobId} aborted"); + return ValueTask.CompletedTask; + } +} + + /// + /// Demonstrates dynamic library loading for a C++ worker: upload a .so at runtime and execute tasks with it. + /// + /// The .so is uploaded once as a blob. Each task carries the blob ID in its task options (LibraryBlobId). + /// The C++ DynamicWorker fetches the blob from the task's data dependencies, writes it to a temp file, + /// dlopen()s it, and dispatches to the function named by Symbol via armonik_call. + /// + /// + /// Configuration (appsettings.json, environment variables, or command-line flags, in increasing priority):
+ /// LibraryPath — absolute path to the .so to upload (e.g. --LibraryPath /path/to/libfoo.so)
+ /// Partition — ArmoniK partition to target (e.g. --Partition cppdynamic)
+ /// Symbol — function_name passed to armonik_call (e.g. --Symbol multiply) + ///
+ ///
+ internal static class Program + { + private static async Task RunAsync(string[] args) + { + var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", + optional: true, + reloadOnChange: false) + .AddEnvironmentVariables() + // Command-line args have the highest priority. + // Usage: --LibraryPath /path/to/lib.so --Partition cppdynamic --Symbol multiply + .AddCommandLine(args); + + var config = builder.Build(); + + // Read parameters — command-line > environment > appsettings.json. + var libraryPath = config["LibraryPath"] + ?? throw new InvalidOperationException( + "LibraryPath is required. Set it in appsettings.json, as an environment variable, or pass --LibraryPath."); + var partition = config["Partition"]!; + var symbol = config["Symbol"]!; + + var loggerFactory = new LoggerFactory([ + new SerilogLoggerProvider(new LoggerConfiguration().ReadFrom.Configuration(config) + .CreateLogger()), + ], + new LoggerFilterOptions().AddFilter("Grpc", + LogLevel.Error)); + var logger = loggerFactory.CreateLogger(nameof(Program)); + + logger.Log(LogLevel.Information, "Library : {libraryPath}", libraryPath); + logger.Log(LogLevel.Information, "Partition: {partition}", partition); + logger.Log(LogLevel.Information, "Symbol : {symbol}", symbol); + + var properties = new Properties(config); + var taskConfiguration = new TaskConfiguration(3, 1, partition, TimeSpan.FromSeconds(300)); + var client = new ArmoniKClient(properties, loggerFactory); + + logger.Log(LogLevel.Information, "Opening session on partition '{partition}'...", partition); + var sessionHandle = await client.CreateSessionAsync([partition], + taskConfiguration, + true) + .ConfigureAwait(false); + logger.Log(LogLevel.Information, "Session opened: {sessionId}", sessionHandle.SessionInfo.SessionId); + + // Upload the .so as a blob. The blob ID is attached to every task so the worker + // can retrieve the library content from its data dependencies at execution time. + var content = await File.ReadAllBytesAsync(libraryPath).ConfigureAwait(false); + var libraryBlob = await client.BlobService.CreateBlobAsync(sessionHandle, + "cppLibrary", + content, + false, + CancellationToken.None) + .ConfigureAwait(false); + + logger.Log(LogLevel.Information, "Uploaded library blob: {blobId}", libraryBlob.BlobId); + + // Symbol is stored in the task options under the "Symbol" key. + // The DynamicWorker passes it as function_name to armonik_call so the library + // can dispatch to the right handler. + var workerLibrary = new DynamicLibrary + { + Symbol = symbol, + LibraryBlobId = libraryBlob.BlobId, + }; + + var callBack = new Callback(); + var taskDefinition = new TaskDefinition().WithTaskOptions(taskConfiguration) + .WithLibrary(workerLibrary) + .WithInput("num1", + BlobDefinition.FromString("two", + 2.ToString())) + .WithInput("num2", + BlobDefinition.FromString("three", + 3.ToString())) + .WithOutput("result", + BlobDefinition.CreateOutput("resultBlob").WithCallback(callBack)); + + logger.Log(LogLevel.Information, "Submitting task (symbol: {symbol})...", symbol); + sessionHandle.Submit(taskDefinition); + + logger.Log(LogLevel.Information, "Waiting for task result..."); + await sessionHandle.WaitCallbacksAsync().ConfigureAwait(false); + + var resultString = Encoding.UTF8.GetString(callBack.Result); + logger.Log(LogLevel.Information, "Result: {result}", resultString); + + logger.Log(LogLevel.Information, "Closing session {sessionId}...", sessionHandle.SessionInfo.SessionId); + await sessionHandle.CloseSessionAsync(CancellationToken.None).ConfigureAwait(false); + logger.Log(LogLevel.Information, "Session {sessionId} closed.", sessionHandle.SessionInfo.SessionId); + } + + private static async Task Main(string[] args) + { + try + { + await RunAsync(args).ConfigureAwait(false); + } + catch (Exception ex) + { + await Console.Error.WriteLineAsync($"Error: {ex.Message}"); + Environment.Exit(1); + } + } + } diff --git a/csharp/sdk/CppDynamicLibrary/appsettings.json b/csharp/sdk/CppDynamicLibrary/appsettings.json new file mode 100644 index 00000000..7877a9fc --- /dev/null +++ b/csharp/sdk/CppDynamicLibrary/appsettings.json @@ -0,0 +1,66 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Grpc": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + }, + "Serilog": { + "Using": [ + "Serilog.Sinks.Console" + ], + "MinimumLevel": "Information", + "WriteTo": [ + { + "Name": "Console" + } + ], + "Enrich": [ + "FromLogContext", + "WithMachineName", + "WithThreadId" + ], + "Destructure": [ + { + "Name": "ToMaximumDepth", + "Args": { + "maximumDestructuringDepth": 4 + } + }, + { + "Name": "ToMaximumStringLength", + "Args": { + "maximumStringLength": 100 + } + }, + { + "Name": "ToMaximumCollectionCount", + "Args": { + "maximumCollectionCount": 10 + } + } + ], + "Properties": { + "Application": "UsageExample" + } + }, + "Grpc": { + "EndPoint": "http://localhost:5001" + }, + "LibraryPath": "/path/to/libworker.so", + "Partition": "default", + "Symbol": "multiply", + "ApplicationConfig": { + "DebugMode": false, + "AppName": "CppDynamicLibrary", + "appVersion": "1.0.0" + } +}