Skip to content
This repository was archived by the owner on Nov 20, 2023. It is now read-only.
Open
2 changes: 2 additions & 0 deletions src/Microsoft.Tye.Core/HostOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public class HostOptions

public List<string> Debug { get; } = new List<string>();

public List<string> NoStart { get; } = new List<string>();

public string? DistributedTraceProvider { get; set; }

public bool Docker { get; set; }
Expand Down
50 changes: 49 additions & 1 deletion src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<th>Replicas</th>
<th>Restarts</th>
<th>Logs</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
Expand Down Expand Up @@ -92,16 +93,38 @@
<td>@service.Replicas.Count/@service.Description.Replicas</td>
<td>@service.Restarts</td>
<td><NavLink href="@logsPath">View</NavLink></td>
<td>
@if (CanStartStop(service))
{
if (service.Replicas.Count == 0)
{
<button @onclick="async () => await StartServiceAsync(service)" class="btn btn-default btn-xs">
<span class="oi oi-media-play"></span>
</button>
}
else
{
<button @onclick="async () => await StopServiceAsync(service)" class="btn btn-default btn-xs">
<span class="oi oi-media-stop"></span>
</button>
}
}
</td>
}
</tr>
}
</tbody>
</table>

@code {

static readonly ServiceType[] stopables = new[] { ServiceType.Container, ServiceType.Executable, ServiceType.Project, ServiceType.Function };
private List<IDisposable> _subscriptions = new List<IDisposable>();

bool CanStartStop(Service? service)
{
return service != null && stopables.Contains(service.ServiceType);
}

string GetUrl(ServiceBinding b)
{
return $"{(b.Protocol ?? "tcp")}://{b.Host ?? "localhost"}:{b.Port}";
Expand All @@ -120,6 +143,31 @@
InvokeAsync(() => StateHasChanged());
}

private async Task StartServiceAsync(Service service)
{
if (service.ServiceType == ServiceType.Container)
{
await DockerRunner.RestartContainerAsync(service);
}
else
{
await ProcessRunner.RestartService(service);
}
}

private async Task StopServiceAsync(Service service)
{
if (service.ServiceType == ServiceType.Container)
{
await DockerRunner.StopContainerAsync(service);
}
else
{
await ProcessRunner.KillProcessAsync(service);
}
}


void IDisposable.Dispose()
{
_subscriptions.ForEach(d => d.Dispose());
Expand Down
55 changes: 47 additions & 8 deletions src/Microsoft.Tye.Hosting/DockerRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ public class DockerRunner : IApplicationProcessor
private readonly ILogger _logger;

private readonly ReplicaRegistry _replicaRegistry;
private readonly DockerRunnerOptions _options;

public DockerRunner(ILogger logger, ReplicaRegistry replicaRegistry)
public DockerRunner(ILogger logger, ReplicaRegistry replicaRegistry, DockerRunnerOptions options)
{
_logger = logger;
_replicaRegistry = replicaRegistry;
_options = options;
}

public async Task StartAsync(Application application)
Expand Down Expand Up @@ -537,15 +539,21 @@ Task DockerRunAsync(CancellationToken cancellationToken)
return Task.WhenAll(tasks);
}

var dockerInfo = new DockerInformation();
async Task BuildAndRunAsync(CancellationToken cancellationToken)
{
await DockerBuildAsync(cancellationToken);

await DockerRunAsync(cancellationToken);
}

var dockerInfo = new DockerInformation();
dockerInfo.Task = BuildAndRunAsync(dockerInfo.StoppingTokenSource.Token);
dockerInfo.SetBuildAndRunTask(BuildAndRunAsync);

if (!_options.ManualStartServices &&
!(_options.ServicesNotToStart?.Contains(service.Description.Name, StringComparer.OrdinalIgnoreCase) ?? false))
{
dockerInfo.BuildAndRun();
}

service.Items[typeof(DockerInformation)] = dockerInfo;
}
Expand Down Expand Up @@ -587,22 +595,53 @@ private static void PrintStdOutAndErr(Service service, string replica, ProcessRe
}
}

private Task StopContainerAsync(Service service)
public static async Task RestartContainerAsync(Service service)
{
if (service.Items.TryGetValue(typeof(DockerInformation), out var value) && value is DockerInformation di)
{
await StopContainerAsync(service);

di.BuildAndRun();
service.Restarts++;
await di.Task;
}
}

public static Task StopContainerAsync(Service service)
{
if (service.Items.TryGetValue(typeof(DockerInformation), out var value) && value is DockerInformation di)
{
di.StoppingTokenSource.Cancel();
di.CancelAndResetStoppingTokenSource();
return di.Task ?? Task.CompletedTask;

return di.Task;
}

return Task.CompletedTask;
}

private class DockerInformation
{
public Task Task { get; set; } = default!;
public CancellationTokenSource StoppingTokenSource { get; } = new CancellationTokenSource();
private Func<CancellationToken, Task>? _buildAndRunAsync;

public Task Task { get; private set; } = default!;
public CancellationTokenSource StoppingTokenSource { get; private set; } = new CancellationTokenSource();

public void SetBuildAndRunTask(Func<CancellationToken, Task> func)
{
_buildAndRunAsync = func;
}

public void BuildAndRun()
{
Task = _buildAndRunAsync?.Invoke(StoppingTokenSource.Token) ?? Task.CompletedTask;
}

internal void CancelAndResetStoppingTokenSource()
{
StoppingTokenSource.Cancel();
StoppingTokenSource.Dispose();
StoppingTokenSource = new CancellationTokenSource();
}
}

private class DockerApplicationInformation
Expand Down
24 changes: 24 additions & 0 deletions src/Microsoft.Tye.Hosting/DockerRunnerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Linq;

namespace Microsoft.Tye.Hosting
{
public class DockerRunnerOptions
{
public bool ManualStartServices { get; set; }
public string[]? ServicesNotToStart { get; set; }

public static DockerRunnerOptions FromHostOptions(HostOptions options)
{
return new DockerRunnerOptions
{
ManualStartServices = options.NoStart?.Contains("*", StringComparer.OrdinalIgnoreCase) ?? false,
ServicesNotToStart = options.NoStart?.ToArray()
};
}
}
}
104 changes: 70 additions & 34 deletions src/Microsoft.Tye.Hosting/ProcessRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,11 @@ service.Description.RunInfo is ProjectRunInfo project2 &&
}

private void LaunchService(Application application, Service service)

{
var serviceDescription = service.Description;
var processInfo = new ProcessInfo(new Task[service.Description.Replicas]);
var serviceName = serviceDescription.Name;
var processInfo = (service.Items.ContainsKey(typeof(ProcessInfo)) ? (ProcessInfo?)service.Items[typeof(ProcessInfo)] : null)
?? new ProcessInfo(new Task[service.Description.Replicas]);
var serviceName = service.Description.Name;

// Set by BuildAndRunService
var args = service.Status.Args!;
Expand Down Expand Up @@ -258,7 +259,7 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string?

var backOff = TimeSpan.FromSeconds(5);

while (!processInfo.StoppedTokenSource.IsCancellationRequested)
while (!processInfo!.StoppedTokenSource.IsCancellationRequested)
{
var replica = serviceName + "_" + Guid.NewGuid().ToString().Substring(0, 10).ToLower();
var status = new ProcessStatus(service, replica);
Expand Down Expand Up @@ -297,7 +298,7 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string?
try
{
service.Logs.OnNext($"[{replica}]:{path} {copiedArgs}");
var processInfo = new ProcessSpec
var processSpec = new ProcessSpec
{
Executable = path,
WorkingDirectory = workingDirectory,
Expand Down Expand Up @@ -351,8 +352,10 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string?
// Only increase backoff when not watching project as watch will wait for file changes before rebuild.
backOff *= 2;
}

service.Restarts++;
if (!processInfo.StoppedTokenSource.IsCancellationRequested)
{
service.Restarts++;
}

service.Replicas.TryRemove(replica, out var _);
service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Removed, status));
Expand Down Expand Up @@ -385,7 +388,7 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string?
environment["DOTNET_WATCH"] = "1";

await new DotNetWatcher(_logger)
.WatchAsync(processInfo, fileSetFactory, replica, status.StoppingTokenSource.Token);
.WatchAsync(processSpec, fileSetFactory, replica, status.StoppingTokenSource.Token);
}
else if (_options.Watch && (service.Description.RunInfo is AzureFunctionRunInfo azureFunctionRunInfo) && !string.IsNullOrEmpty(azureFunctionRunInfo.ProjectFile))
{
Expand All @@ -397,11 +400,11 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string?
environment["DOTNET_WATCH"] = "1";

await new DotNetWatcher(_logger)
.WatchAsync(processInfo, fileSetFactory, replica, status.StoppingTokenSource.Token);
.WatchAsync(processSpec, fileSetFactory, replica, status.StoppingTokenSource.Token);
}
else
{
await ProcessUtil.RunAsync(processInfo, status.StoppingTokenSource.Token, throwOnError: false);
await ProcessUtil.RunAsync(processSpec, status.StoppingTokenSource.Token, throwOnError: false);
}
}
catch (Exception ex)
Expand Down Expand Up @@ -429,50 +432,77 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string?
}
}

if (serviceDescription.Bindings.Count > 0)
void Start()
{
// Each replica is assigned a list of internal ports, one mapped to each external
// port
for (int i = 0; i < serviceDescription.Replicas; i++)
if (service.Description!.Bindings.Count > 0)
{
var ports = new List<(int, int, string?, string?)>();
foreach (var binding in serviceDescription.Bindings)
// Each replica is assigned a list of internal ports, one mapped to each external
// port
for (int i = 0; i < service.Description.Replicas; i++)
{
if (binding.Port == null)
var ports = new List<(int, int, string?, string?)>();
foreach (var binding in service.Description.Bindings)
{
continue;
if (binding.Port == null)
{
continue;
}

ports.Add((binding.Port.Value, binding.ReplicaPorts[i], binding.Protocol, binding.Host));
}

ports.Add((binding.Port.Value, binding.ReplicaPorts[i], binding.Protocol, binding.Host));
processInfo!.Tasks[i] = RunApplicationAsync(ports, args);
}
}
else
{
for (int i = 0; i < service.Description.Replicas; i++)
{
processInfo!.Tasks[i] = RunApplicationAsync(Enumerable.Empty<(int, int, string?, string?)>(), args);
}

processInfo.Tasks[i] = RunApplicationAsync(ports, args);
}
}

processInfo.Start = Start;
service.Items[typeof(ProcessInfo)] = processInfo;
if (!_options.ManualStartServices && !(_options.ServicesNotToStart?.Contains(serviceName, StringComparer.OrdinalIgnoreCase) ?? false))
{
processInfo.Start();
}
else
{
for (int i = 0; i < service.Description.Replicas; i++)
for (int i = 0; i < processInfo.Tasks.Length; i++)
{
processInfo.Tasks[i] = RunApplicationAsync(Enumerable.Empty<(int, int, string?, string?)>(), args);
processInfo.Tasks[i] = Task.CompletedTask;
}
}
}

service.Items[typeof(ProcessInfo)] = processInfo;
public static async Task RestartService(Service service)
{
if (service.Items.TryGetValue(typeof(ProcessInfo), out var stateObj) && stateObj is ProcessInfo state)
{
await KillProcessAsync(service);
service.Restarts++;
state.Start?.Invoke();
await Task.WhenAll(state.Tasks);
}
}

private Task KillRunningProcesses(IDictionary<string, Service> services)
public static async Task KillProcessAsync(Service service)
{
static Task KillProcessAsync(Service service)
if (service.Items.TryGetValue(typeof(ProcessInfo), out var stateObj) && stateObj is ProcessInfo state)
{
if (service.Items.TryGetValue(typeof(ProcessInfo), out var stateObj) && stateObj is ProcessInfo state)
{
// Cancel the token before stopping the process
state.StoppedTokenSource.Cancel();
// Cancel the token before stopping the process
state.StoppedTokenSource?.Cancel();

return Task.WhenAll(state.Tasks);
}
return Task.CompletedTask;
await Task.WhenAll(state.Tasks);
state.ResetStoppedTokenSource();
}
}

private Task KillRunningProcesses(IDictionary<string, Service> services)
{

var index = 0;
var tasks = new Task[services.Count];
Expand Down Expand Up @@ -541,7 +571,13 @@ public ProcessInfo(Task[] tasks)

public Task[] Tasks { get; }

public CancellationTokenSource StoppedTokenSource { get; } = new CancellationTokenSource();
public CancellationTokenSource StoppedTokenSource { get; private set; } = new CancellationTokenSource();
public Action? Start { get; internal set; }
internal void ResetStoppedTokenSource()
{
StoppedTokenSource.Dispose();
StoppedTokenSource = new CancellationTokenSource();
}
}

private class ProjectGroup
Expand Down
Loading