Skip to content
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

make sure yagna and ya-provider are killed when facade is stopped #12

Merged
merged 7 commits into from
Nov 21, 2023
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
27 changes: 13 additions & 14 deletions FacadeApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.ComponentModel;
using System.Diagnostics;

using CommandLine;
using GolemLib;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -38,18 +40,19 @@ static async Task Main(string[] args)
logger.LogInformation("Path: " + golemPath);
logger.LogInformation("DataDir: " + (dataDir ?? ""));

await using (var golem = new Golem.Golem(golemPath, dataDir, loggerFactory))
{

golem.PropertyChanged += new PropertyChangedHandler(logger).For(nameof(IGolem.Status));
var binaries = Path.Combine(golemPath, "golem");
dataDir = Path.Combine(golemPath, "golem-data");

await using (var golem = new Golem.Golem(binaries, dataDir, loggerFactory))
{
golem.PropertyChanged += new PropertyChangedHandler(logger).For(nameof(IGolem.Status));

bool end = false;

do
{
Console.WriteLine("Start/Stop/End?");
var line = Console.ReadLine();
do
{
Console.WriteLine("Start/Stop/End?");
var line = Console.ReadLine();

switch (line)
{
Expand Down Expand Up @@ -99,17 +102,13 @@ public PropertyChangedEventHandler For(string name)

private void Status_PropertyChangedHandler(object? sender, PropertyChangedEventArgs e)
{
if (sender is not Golem.Golem golem || e.PropertyName != "Status")
logger.LogError($"Type or {e.PropertyName} is not supported in this context");
else
if (sender is Golem.Golem golem && e.PropertyName != "Status")
logger.LogInformation($"Status property has changed: {e.PropertyName} to {golem.Status}");
}

private void Activities_PropertyChangedHandler(object? sender, PropertyChangedEventArgs e)
{
if (sender is not Golem.Golem golem || e.PropertyName != "Activities")
logger.LogError($"Type or {e.PropertyName} is not supported in this context");
else
if (sender is Golem.Golem golem && e.PropertyName != "Activities")
logger.LogInformation($"Activities property has changed: {e.PropertyName}. Current job: {golem.CurrentJob}");
}

Expand Down
4 changes: 2 additions & 2 deletions FacadeApp/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"profiles": {
"FacadeApp": {
"commandName": "Project",
"commandLineArgs": "-g /gome/skrotki/golem/gamerhash/modules"
"commandLineArgs": "-g c:\\\\git\\\\gamerhash\\\\modules"
}
}
}
}
69 changes: 24 additions & 45 deletions Golem/Golem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Golem.Model;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.Collections.Specialized;

namespace Golem
{
Expand Down Expand Up @@ -133,7 +134,6 @@ public async Task Start()

bool openConsole = false;


var yagnaOptions = YagnaOptionsFactory.CreateStartupOptions(openConsole);

var success = await StartupYagnaAsync(yagnaOptions);
Expand Down Expand Up @@ -223,7 +223,26 @@ private async Task<bool> StartupYagnaAsync(YagnaStartupOptions yagnaOptions)

_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", yagnaOptions.AppKey);

Thread.Sleep(700);
account = await WaitForIdentityAsync();

//TODO what if activityLoop != null?
this._activityLoop = StartActivityLoop();

Yagna.PaymentService.Init(yagnaOptions.Network, PaymentDriver.ERC20.Id, account ?? "");

return success;
}

public bool StartupProvider(YagnaStartupOptions yagnaOptions)
{
Provider.PresetConfig.InitilizeDefaultPreset();

return Provider.Run(yagnaOptions.AppKey, Network.Goerli, yagnaOptions.YagnaApiUrl, yagnaOptions.OpenConsole, true);
}

async Task<string?> WaitForIdentityAsync()
{
string? identity = null;

//yagna is starting and /me won't work until all services are running
for (int tries = 0; tries < 300; ++tries)
Expand Down Expand Up @@ -251,8 +270,8 @@ private async Task<bool> StartupYagnaAsync(YagnaStartupOptions yagnaOptions)
//sanity check
if (meInfo != null)
{
if (account == null || account.Length == 0)
account = meInfo.Identity;
if (identity == null || identity.Length == 0)
identity = meInfo.Identity;
break;
}
throw new Exception("Failed to get key");
Expand All @@ -263,47 +282,7 @@ private async Task<bool> StartupYagnaAsync(YagnaStartupOptions yagnaOptions)
// consciously swallow the exception... presumably REST call error...
}
}

//TODO what if activityLoop != null?
this._activityLoop = StartActivityLoop();

Yagna.PaymentService.Init(yagnaOptions.Network, PaymentDriver.ERC20.Id, account ?? "");

return success;
}

public bool StartupProvider(YagnaStartupOptions yagnaOptions)
{
var presets = Provider.PresetConfig.ActivePresetsNames;
if (!presets.Contains(Provider.PresetConfig.DefaultPresetName))
{
// Duration=0.0001 CPU=0.0001 "Init price=0.0000000000000001"
var coefs = new Dictionary<string, decimal>
{
{ "Duration", 0.0001m },
{ "CPU", 0.0001m },
// { "Init price", 0.0000000000000001m },
};
// name "ai" as defined in plugins/*.json
var preset = new Preset(Provider.PresetConfig.DefaultPresetName, "ai-dummy", coefs);

Provider.PresetConfig.AddPreset(preset, out string args, out string info);
Console.WriteLine($"Args {args}");
Console.WriteLine($"Args {info}");

}
Provider.PresetConfig.ActivatePreset(Provider.PresetConfig.DefaultPresetName);

foreach (string preset in presets)
{
if (preset != Provider.PresetConfig.DefaultPresetName)
{
Provider.PresetConfig.DeactivatePreset(preset);
}
Console.WriteLine($"Preset {preset}");
}

return Provider.Run(yagnaOptions.AppKey, Network.Goerli, yagnaOptions.YagnaApiUrl, true, true);
return identity;
}

void OnYagnaErrorDataRecv(object sender, DataReceivedEventArgs e)
Expand Down
161 changes: 161 additions & 0 deletions Golem/Tools/ChildProcessTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Golem.Tools
{
public static class ChildProcessTracker
{
/// <summary>
/// Add the process to be tracked. If our current process is killed, the child processes
/// that we are tracking will be automatically killed, too. If the child process terminates
/// first, that's fine, too.</summary>
/// <param name="process"></param>
public static void AddProcess(Process process)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
// this is a light workaround for Linux that does not work in all cases, e.g. when the process is killed by SIGKILL
// but works OK in most cases when a process is killed by SIGTERM or SIGINT
// it should be possible to add code that would handle this case from inside the child process, if needed,
// with something like prctl(PR_SET_PDEATHSIG, SIGTERM) (send SIGTERM when parent dies)
AppDomain.CurrentDomain.ProcessExit += (s, e) => process.Kill(true);
}
else
{
if (s_jobHandle != nint.Zero)
{
bool success = true;
try
{
success = AssignProcessToJobObject(s_jobHandle, process.Handle);
}
catch
{
success = false;
}
if (!success && !process.HasExited)
Console.WriteLine("Failed to track process {0}. Error: {1}", process.Id, new Win32Exception().Message);
}
}
}

static ChildProcessTracker()
{
// This feature requires Windows 8 or later. To support Windows 7 requires
// registry settings to be added if you are using Visual Studio plus an
// app.manifest change.
// https://stackoverflow.com/a/4232259/386091
// https://stackoverflow.com/a/9507862/386091
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || Environment.OSVersion.Version < new Version(6, 2))
return;

// The job name is optional (and can be null) but it helps with diagnostics.
// If it's not null, it has to be unique. Use SysInternals' Handle command-line
// utility: handle -a ChildProcessTracker
string jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id;
s_jobHandle = CreateJobObject(nint.Zero, jobName);

var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();

// This is the key flag. When our process is killed, Windows will automatically
// close the job handle, and when that happens, we want the child processes to
// be killed, too.
info.LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;

var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
extendedInfo.BasicLimitInformation = info;

int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
nint extendedInfoPtr = Marshal.AllocHGlobal(length);
try
{
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation,
extendedInfoPtr, (uint)length))
{
throw new Win32Exception();
}
}
finally
{
Marshal.FreeHGlobal(extendedInfoPtr);
}
}

[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern nint CreateJobObject(nint lpJobAttributes, string name);

[DllImport("kernel32.dll")]
static extern bool SetInformationJobObject(nint job, JobObjectInfoType infoType,
nint lpJobObjectInfo, uint cbJobObjectInfoLength);

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AssignProcessToJobObject(nint job, nint process);


// Windows will automatically close any open job handles when our process terminates.
// This can be verified by using SysInternals' Handle utility. When the job handle
// is closed, the child processes will be killed.
private static readonly nint s_jobHandle;
}

public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}

[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public long PerProcessUserTimeLimit;
public long PerJobUserTimeLimit;
public JOBOBJECTLIMIT LimitFlags;
public nuint MinimumWorkingSetSize;
public nuint MaximumWorkingSetSize;
public uint ActiveProcessLimit;
public long Affinity;
public uint PriorityClass;
public uint SchedulingClass;
}

[Flags]
public enum JOBOBJECTLIMIT : uint
{
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
}

[StructLayout(LayoutKind.Sequential)]
public struct IO_COUNTERS
{
public ulong ReadOperationCount;
public ulong WriteOperationCount;
public ulong OtherOperationCount;
public ulong ReadTransferCount;
public ulong WriteTransferCount;
public ulong OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public nuint ProcessMemoryLimit;
public nuint JobMemoryLimit;
public nuint PeakProcessMemoryUsed;
public nuint PeakJobMemoryUsed;
}
}
6 changes: 6 additions & 0 deletions Golem/Yagna/EnvironmentBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ public EnvironmentBuilder WithYagnaApiUrl(string s)
return this;
}

public EnvironmentBuilder WithYagnaAppKey(string s)
{
env["YAGNA_APPKEY"] = s;
return this;
}

public EnvironmentBuilder WithYaPaymentNetworkGroup(string s)
{
env["YA_PAYMENT_NETWORK_GROUP"] = s;
Expand Down
34 changes: 34 additions & 0 deletions Golem/Yagna/PresetConfigService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,40 @@ internal PresetConfigService(Provider parent)
_parent = parent;
}

public void InitilizeDefaultPreset()
{
var presets = ActivePresetsNames;
if (!presets.Contains(DefaultPresetName))
{

var coefs = new Dictionary<string, decimal>
{
{ "ai-runtime.requests", 0 },
{ "golem.usage.duration_sec", 0 },
{ "golem.usage.gpu-sec", 0 },
{ "Initial", 0 }
};

// name "ai" as defined in plugins/*.json
var preset = new Preset(DefaultPresetName, "ai", coefs);

AddPreset(preset, out string args, out string info);
Console.WriteLine($"Args {args}");
Console.WriteLine($"Args {info}");

}
ActivatePreset(DefaultPresetName);

foreach (string preset in presets)
{
if (preset != DefaultPresetName)
{
DeactivatePreset(preset);
}
Console.WriteLine($"Preset {preset}");
}
}

public IList<string> ActivePresetsNames
{
get
Expand Down
Loading
Loading