Skip to content

Commit

Permalink
Hold a separate kernel instance for each notebook to avoid interference
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveSandersonMS committed Oct 28, 2020
1 parent ee026af commit cf84a47
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 21 deletions.
1 change: 1 addition & 0 deletions Api/Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.9" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.3" />
<PackageReference Include="Microsoft.DotNet.Interactive.CSharp" Version="1.0.0-beta.20426.1" />
<PackageReference Include="FSharp.Compiler.Private.Scripting" Version="11.0.0-beta.20428.1" />
Expand Down
15 changes: 8 additions & 7 deletions Api/KernelFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ namespace blazoract.Api
{
public class KernelFunction
{
private CompositeKernel _kernel;
public KernelFunction(CompositeKernel kernel)
private KernelStore _kernels;
public KernelFunction(KernelStore kernels)
{
this._kernel = kernel;
_kernels = kernels;
}

[FunctionName("RunCode")]
Expand All @@ -33,9 +33,9 @@ public async Task<IActionResult> RunCode(
ILogger log)
{
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var cell = JsonConvert.DeserializeObject<ExecuteRequest>(requestBody);

var request = await _kernel.SendAsync(new SubmitCode(cell.Input), new CancellationToken());
var executeRequest = JsonConvert.DeserializeObject<ExecuteRequest>(requestBody);
var kernel = _kernels.GetKernelForNotebook(executeRequest.NotebookId);
var request = await kernel.SendAsync(new SubmitCode(executeRequest.Code), CancellationToken.None);
var result = new ExecuteResult();
request.KernelEvents.Subscribe(x =>
{
Expand All @@ -62,7 +62,8 @@ public async Task<IActionResult> GetCompletions(
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var completionRequest = JsonConvert.DeserializeObject<GetCompletionsRequest>(requestBody);

var request = await _kernel.SendAsync(new RequestCompletions(completionRequest.Code, new LinePosition(completionRequest.LineNumber - 1, completionRequest.Column - 1)));
var kernel = _kernels.GetKernelForNotebook(completionRequest.NotebookId);
var request = await kernel.SendAsync(new RequestCompletions(completionRequest.Code, new LinePosition(completionRequest.LineNumber - 1, completionRequest.Column - 1)));
var result = Array.Empty<Suggestion>();
request.KernelEvents.Subscribe(x =>
{
Expand Down
42 changes: 42 additions & 0 deletions Api/KernelStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.DotNet.Interactive.CSharp;
using Microsoft.DotNet.Interactive;
using System.Collections.Concurrent;
using Microsoft.Extensions.Caching.Memory;
using System;

[assembly: FunctionsStartup(typeof(blazoract.Api.Startup))]

namespace blazoract.Api
{
// To avoid interference across notebooks, we need a separate kernel instance per notebook.
// Set up a cache in memory that holds a certain maximum number of instances, and evicts
// entries if they become idle for longer than a certain period.

public class KernelStore
{
const int MaxEntries = 1024;
const int MaxIdleMinutes = 15;

private MemoryCache _kernelsCache = new MemoryCache(new MemoryCacheOptions
{
SizeLimit = MaxEntries
});

private ConcurrentDictionary<string, CompositeKernel> _kernels
= new ConcurrentDictionary<string, CompositeKernel>();

public CompositeKernel GetKernelForNotebook(string notebookId)
{
return _kernelsCache.GetOrCreate(notebookId, entry =>
{
entry.SetSlidingExpiration(TimeSpan.FromMinutes(MaxIdleMinutes));
entry.Size = 1;
return new CompositeKernel()
{
new CSharpKernel().UseDefaultFormatting().UseDotNetVariableSharing()
};
});
}
}
}
8 changes: 2 additions & 6 deletions Api/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.DotNet.Interactive.CSharp;
using Microsoft.DotNet.Interactive;

[assembly: FunctionsStartup(typeof(blazoract.Api.Startup))]

Expand All @@ -11,9 +9,7 @@ public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddSingleton<CompositeKernel>(new CompositeKernel() {
new CSharpKernel().UseDefaultFormatting().UseDotNetVariableSharing()
});
builder.Services.AddSingleton<KernelStore>();
}
}
}
}
6 changes: 3 additions & 3 deletions Client/Data/NotebookService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public NotebookService(ILocalStorageService storage, HttpClient http)
var initialContent = new List<Cell>();
for (var i = 0; i < 100; i++)
{
initialContent.Add(new Cell($"{i} * {i}", i));
initialContent.Add(new Cell(id, $"{i} * {i}", i));
}
notebook.Cells = initialContent;
_storage.SetItemAsync("_default_notebook", notebook);
Expand All @@ -57,7 +57,7 @@ public async Task<string> CreateNewNotebook()
var id = Guid.NewGuid().ToString("N");
var title = "New notebook";
var notebook = new Notebook(title, id);
notebook.Cells = new List<Cell>() { new Cell("", 0) };
notebook.Cells = new List<Cell>() { new Cell(id, "// Type your code here", 0) };
await _storage.SetItemAsync(id, notebook);

var notebooks = await _storage.GetItemAsync<List<string>>("blazoract-notebooks") ?? new List<string>();
Expand All @@ -71,7 +71,7 @@ public async Task<string> CreateNewNotebook()
public async Task<Notebook> AddCell(string id, string content, CellType type, int position)
{
var notebook = await GetById(id);
notebook.Cells.Insert(position, new Cell(content, position, type));
notebook.Cells.Insert(position, new Cell(id, content, position, type));
await _storage.SetItemAsync(id, notebook);
OnChange.Invoke();
return notebook;
Expand Down
3 changes: 2 additions & 1 deletion Client/Shared/CodeCell.razor
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
try
{
isEvaluating = true;
var request = new ExecuteRequest(cell.Content);
var request = new ExecuteRequest(cell.NotebookId, cell.Content);
var response = await Http.PostAsJsonAsync("api/code/run", request);
result = await response.Content.ReadFromJsonAsync<ExecuteResult>();
}
Expand All @@ -76,6 +76,7 @@
{
var response = await Http.PostAsJsonAsync("api/code/completions", new GetCompletionsRequest
{
NotebookId = cell.NotebookId,
Code = value,
Column = position.Column,
LineNumber = position.LineNumber
Expand Down
6 changes: 5 additions & 1 deletion Shared/Cell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ namespace blazoract.Shared
{
public class Cell
{
public Cell(string content, int position, CellType type = CellType.Code)
public Cell(string notebookId, string content, int position, CellType type = CellType.Code)
{
NotebookId = notebookId;
Content = content;
Position = position;
Type = type;
}

public string NotebookId { get; set; }

public string Content { get; set; }

public CellType Type { get; set; }
Expand Down
10 changes: 7 additions & 3 deletions Shared/ExecuteRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ namespace blazoract.Shared
{
public class ExecuteRequest
{
public ExecuteRequest(string input)
public ExecuteRequest(string notebookId, string code)
{
Input = input;
NotebookId = notebookId;
Code = code;
}
public string Input { get; set; }

public string NotebookId { get; set; }

public string Code { get; set; }
}
}
1 change: 1 addition & 0 deletions Shared/GetCompletionsRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{
public class GetCompletionsRequest
{
public string NotebookId { get; set; }
public string Code { get; set; }
public int LineNumber { get; set; }
public int Column { get; set; }
Expand Down

0 comments on commit cf84a47

Please sign in to comment.