Skip to content

Commit b94a559

Browse files
Add process state logic in Windows service, connect to daemon using namedpipe, setup client and server contract
1 parent 1a83e5f commit b94a559

File tree

15 files changed

+189
-7
lines changed

15 files changed

+189
-7
lines changed

.vscode/launch.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@
2323
"type": "coreclr",
2424
"request": "attach",
2525
"processId": "${command:pickProcess}"
26+
},
27+
{
28+
"name": ".NET Core WindowService (console)",
29+
"type": "coreclr",
30+
"request": "launch",
31+
"preLaunchTask": "windows_service_build",
32+
// If you have changed target frameworks, make sure to update the program path.
33+
"program": "${workspaceFolder}/src/microstack.Daemon.WindowsService/bin/Debug/netcoreapp3.1/microstack.Daemon.WindowsService.dll",
34+
"cwd": "${workspaceFolder}/src/microstack.Daemon.WindowsService",
35+
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
36+
"console": "internalConsole",
37+
"stopAtEntry": false
2638
}
2739
]
2840
}

.vscode/tasks.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@
1313
],
1414
"problemMatcher": "$msCompile"
1515
},
16+
{
17+
"label": "windows_service_build",
18+
"command": "dotnet",
19+
"type": "process",
20+
"args": [
21+
"build",
22+
"${workspaceFolder}/src/microstack.Daemon.WindowsService/microstack.Daemon.WindowsService.csproj",
23+
"/property:GenerateFullPaths=true",
24+
"/consoleloggerparameters:NoSummary"
25+
],
26+
"problemMatcher": "$msCompile"
27+
},
1628
{
1729
"label": "publish",
1830
"command": "dotnet",

src/microstack.Daemon.WindowsService/MicroStackListner.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using System;
12
using System.Collections.Concurrent;
3+
using System.Diagnostics;
24
using System.IO.Pipes;
35
using System.Threading;
46
using System.Threading.Tasks;
@@ -9,10 +11,10 @@ namespace microstack.Daemon.WindowsService
911
{
1012
public class MicroStackListner : BackgroundService
1113
{
12-
private BlockingCollection<int> _newProcesses;
13-
public MicroStackListner()
14+
private readonly ProcessStateManager _processStateManager;
15+
public MicroStackListner(ProcessStateManager processStateManager)
1416
{
15-
_newProcesses = new BlockingCollection<int>();
17+
_processStateManager = processStateManager;
1618
}
1719

1820
public override Task StartAsync(CancellationToken cancellationToken)
@@ -24,12 +26,15 @@ protected async override Task ExecuteAsync(CancellationToken stoppingToken)
2426
{
2527
while(!stoppingToken.IsCancellationRequested)
2628
{
29+
var p = Process.GetProcessesByName("microstack");
2730
using (var pipe = new NamedPipeServerStream("microstack_pipe", PipeDirection.InOut, 5))
2831
{
2932
var managedThread = Thread.CurrentThread.ManagedThreadId;
3033
await pipe.WaitForConnectionAsync();
34+
Console.WriteLine("Connected");
3135
var processContract = Serializer.Deserialize<ProcessContract>(pipe);
32-
_newProcesses.Add(processContract.ProcessId);
36+
_processStateManager.AddProcess(processContract.ProcessId, processContract.MicroStackPID);
37+
pipe.Disconnect();
3338
}
3439
}
3540
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace microstack.Daemon.WindowsService
2+
{
3+
public static class Constants
4+
{
5+
public const int GreyInterval = 8;
6+
public const int BlackInterval = 13;
7+
}
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
3+
namespace microstack.Daemon.WindowsService
4+
{
5+
public class ProcessEventArgs : EventArgs
6+
{
7+
public int PID { get; set; }
8+
public ProcessMarkers ProcessMarker { get; set; }
9+
}
10+
}

src/microstack.Daemon.WindowsService/ProcessMarkers.cs renamed to src/microstack.Daemon.WindowsService/Process/ProcessMarkers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ namespace microstack.Daemon.WindowsService
22
{
33
public enum ProcessMarkers
44
{
5-
New,
5+
Green,
66
Grey,
77
Black
88
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Threading;
4+
5+
namespace microstack.Daemon.WindowsService
6+
{
7+
public class ProcessState
8+
{
9+
private Timer _timer;
10+
public int MicroStackPID { get; set; }
11+
public int PID { get; set; }
12+
public ProcessMarkers ProcessMarker { get; set; }
13+
public int Ticks { get; private set; }
14+
15+
public ProcessState()
16+
{
17+
_timer = new Timer(CheckHealth, null, 0, 5);
18+
Ticks = 0;
19+
}
20+
21+
private void CheckHealth(object state)
22+
{
23+
// Check if Microstack is alive
24+
var process = Process.GetProcessById(MicroStackPID);
25+
if (process == null)
26+
{
27+
// Raise event to terminate associated PID
28+
ProcessMarker = ProcessMarkers.Black;
29+
OnProcessTerminationRequest(new ProcessEventArgs(){ PID = PID, ProcessMarker = ProcessMarker });
30+
}
31+
Ticks++;
32+
}
33+
34+
protected void OnProcessTerminationRequest(ProcessEventArgs args)
35+
{
36+
EventHandler<ProcessEventArgs> handler = TerminateProcess;
37+
if (handler != null)
38+
{
39+
handler(null, args);
40+
}
41+
}
42+
43+
public event EventHandler<ProcessEventArgs> TerminateProcess;
44+
}
45+
}

src/microstack.Daemon.WindowsService/ProcessContract.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ namespace microstack.Daemon.WindowsService
66
public class ProcessContract
77
{
88
[ProtoMember(1)]
9+
public int MicroStackPID { get; set; }
10+
11+
[ProtoMember(2)]
912
public int ProcessId { get; set; }
1013
}
1114
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System.Collections.Generic;
2+
using System.Diagnostics;
3+
using System.Linq;
4+
5+
namespace microstack.Daemon.WindowsService
6+
{
7+
public class ProcessStateManager
8+
{
9+
public IList<ProcessState> _states;
10+
public ProcessStateManager()
11+
{
12+
_states = new List<ProcessState>();
13+
}
14+
15+
public void TerminateProcess(object sender, ProcessEventArgs args)
16+
{
17+
var processState = _states.FirstOrDefault(s => s.PID.Equals(args.PID));
18+
if (processState != null)
19+
{
20+
Process.GetProcessById(processState.PID)?.Kill(true);
21+
_states.Remove(processState);
22+
}
23+
}
24+
25+
public void AddProcess(int pid, int microStackPid)
26+
{
27+
var state = new ProcessState()
28+
{
29+
PID = pid,
30+
MicroStackPID = microStackPid,
31+
ProcessMarker = ProcessMarkers.Green,
32+
};
33+
state.TerminateProcess += TerminateProcess;
34+
_states.Add(state);
35+
}
36+
}
37+
}

src/microstack.Daemon.WindowsService/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
1919
.UseWindowsService()
2020
.ConfigureServices((hostContext, services) =>
2121
{
22-
services.AddHostedService<Worker>();
22+
services.AddHostedService<MicroStackListner>();
23+
services.AddSingleton<ProcessStateManager>();
2324
});
2425
}
2526
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using ProtoBuf;
2+
3+
namespace microstack.BackgroundTasks.Models
4+
{
5+
[ProtoContract]
6+
public class ProcessContract
7+
{
8+
[ProtoMember(1)]
9+
public int MicroStackPID { get; set; }
10+
11+
[ProtoMember(2)]
12+
public int ProcessId { get; set; }
13+
}
14+
}

src/microstack/BackgroundTasks/ProcessQueueTask.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,36 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics;
4+
using System.IO.Pipes;
45
using System.Threading;
56
using System.Threading.Tasks;
67
using McMaster.Extensions.CommandLineUtils;
78
using Microsoft.Extensions.Hosting;
9+
using microstack.BackgroundTasks.Models;
810

911
namespace microstack.BackgroundTasks
1012
{
1113
public class ProcessQueueTask : IHostedService, IDisposable
1214
{
15+
private CancellationToken _cancellationToken;
1316
private Timer _timer;
1417
private readonly ProcessSpawnManager _processSpawnManager;
1518
private readonly IConsole _console;
19+
private readonly IApplicationLifetime _lifetime;
1620
private IList<(string Name, Process Process)> _processTuples;
1721

1822
public ProcessQueueTask(ProcessSpawnManager processSpawnManager,
19-
IConsole console)
23+
IConsole console,
24+
IApplicationLifetime lifetime)
2025
{
2126
this._processSpawnManager = processSpawnManager;
2227
_processTuples = new List<(string Name, Process Process)>();
2328
_console = console;
29+
this._lifetime = lifetime;
2430
}
2531
public Task StartAsync(CancellationToken cancellationToken)
2632
{
33+
_cancellationToken = cancellationToken;
2734
_timer = new Timer(SpawnQueuedProcess, null, TimeSpan.Zero, TimeSpan.FromSeconds(2));
2835
return Task.CompletedTask;
2936
}
@@ -39,11 +46,30 @@ public void SpawnQueuedProcess(object state)
3946
{
4047
var process = Process.Start(processInfoObjectTuples.ProcessInfoObject);
4148
_processTuples.Add((processInfoObjectTuples.Name, process));
49+
using(var pipe = new NamedPipeClientStream(".", "microstack_pipe", PipeDirection.InOut, PipeOptions.None))
50+
{
51+
try{
52+
pipe.Connect(5);
53+
ProtoBuf.Serializer.Serialize(pipe, new ProcessContract() {ProcessId = process.Id, MicroStackPID = Process.GetCurrentProcess().Id});
54+
}
55+
catch (TimeoutException ex)
56+
{
57+
_console.ForegroundColor = ConsoleColor.DarkRed;
58+
_console.Out.WriteLine($"Failed to register {processInfoObjectTuples.Name} with PID {process.Id} with Microstack Daemon, please ensure that Microstack Windows service is running by manually starting it or reinstalling Microstack");
59+
}
60+
finally
61+
{
62+
_console.Out.WriteLine($"Killing {processInfoObjectTuples.Name}");
63+
process.Kill(true);
64+
throw new Exception("Failed to connect to Microstack Daemon");
65+
}
66+
}
4267
} catch(Exception ex)
4368
{
4469
_console.ForegroundColor = ConsoleColor.Red;
4570
_console.Out.WriteLine($"Failed to spawn {processInfoObjectTuples.Name}, {ex.Message}");
4671
_console.ResetColor();
72+
_lifetime.StopApplication();
4773
}
4874
}
4975

src/microstack/Commands/MicroStack.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Threading.Tasks;
22
using McMaster.Extensions.CommandLineUtils;
3+
using Microsoft.Extensions.Hosting;
34
using microstack.Commands.SubCommands;
45

56
namespace microstack.Commands
@@ -11,6 +12,12 @@ namespace microstack.Commands
1112
[Subcommand(typeof(New))]
1213
public class MicroStack : BaseCommand
1314
{
15+
private readonly IHostApplicationLifetime _hostApplicationLifetime;
16+
17+
public MicroStack(IHostApplicationLifetime hostApplicationLifetime)
18+
{
19+
this._hostApplicationLifetime = hostApplicationLifetime;
20+
}
1421
protected async override Task<int> OnExecute(CommandLineApplication app)
1522
{
1623
app.ShowHelp();

src/microstack/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Threading;
2+
using McMaster.Extensions.CommandLineUtils;
23
using Microsoft.Extensions.DependencyInjection;
34
using Microsoft.Extensions.Hosting;
45
using microstack.Abstractions;

src/microstack/microstack.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
1111
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.8" />
1212
<PackageReference Include="McMaster.Extensions.Hosting.CommandLine" Version="3.0.0" />
13+
<PackageReference Include="protobuf-net" Version="3.0.29" />
1314
</ItemGroup>
1415

1516
<ItemGroup>

0 commit comments

Comments
 (0)