Skip to content

Commit

Permalink
XXX broken - we can't have nice things
Browse files Browse the repository at this point in the history
nothing is read after the first int32.

trying to use a separate pipe doesn't work because .NET wants to FStat, but
vscode-wasm has a bug with
mountpoint directories having bad permissions
microsoft/vscode-wasm#111
  • Loading branch information
lambdageek committed Aug 2, 2023
1 parent 59921d0 commit 6707ed6
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 99 deletions.
104 changes: 104 additions & 0 deletions src/StructuredLogViewer.Wasi.Engine/Command.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System;
using System.Data;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace StructuredLogViewer.Wasi.Engine;

[JsonPolymorphic(TypeDiscriminatorPropertyName = "command")]
[JsonDerivedType(typeof(QuitCommand), typeDiscriminator: "quit")]
[JsonDerivedType(typeof(RootCommand), typeDiscriminator: "root")]
[JsonDerivedType(typeof(NodeCommand), typeDiscriminator: "node")]
[JsonDerivedType(typeof(ManyNodesCommand), typeDiscriminator: "manyNodes")]
internal abstract class Command
{
public int RequestId { get; set; }
[JsonIgnore]
public abstract CommandType Type { get; }
}

enum CommandType
{
Quit,
Root,
Node,
ManyNodes,
}


internal class QuitCommand : Command
{
public static QuitCommand Default = new();

public override CommandType Type => CommandType.Quit;
}

internal class RootCommand : Command
{
public static RootCommand Default = new();

public override CommandType Type => CommandType.Root;
}

internal class NodeCommand : Command
{
public int NodeId { get; set; }

public override CommandType Type => CommandType.Node;
}

internal class ManyNodesCommand : Command
{
public int NodeId { get; set; }
public int Count { get; set; }

public override CommandType Type => CommandType.ManyNodes;
}

[JsonSourceGenerationOptions(WriteIndented = true, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(Command))]
internal partial class CommandSerializerContext : JsonSerializerContext
{
}

// Stream format:
// ([LENGTH][JSON])*
// where LENGTH is an int32 in littleEndian order and indicates the size of the json payload.
// the json payload is in utf8
internal class CommandParser
{

private readonly Stream _stream;

public CommandParser(Stream s)
{
_stream = s;
}
public Command ParseCommand()
{
Console.Error.WriteLine($"about to read an int32");
Span<byte> lenBuf = stackalloc byte[4];
_stream.ReadExactly(lenBuf);
var len = (lenBuf[0] - 1) | (lenBuf[1] - 1) << 8 | (lenBuf[2] - 1) << 16 | (lenBuf[3] - 1) << 24;
Console.Error.WriteLine($"read an int32 value: {len}");
var buf = new byte[len];
var bytesRead = 0;
while (bytesRead < len)
{
var i = _stream.ReadByte();
if (i < 0)
{
Console.Error.WriteLine($"wanted {len} bytes but got end of stream after {bytesRead}");
throw new IOException("end of stream while wanted more bytes");
}
buf[bytesRead++] = (byte)i;
}
Console.Error.WriteLine($"Wanted {len} bytes, got {bytesRead}");
var s = Encoding.UTF8.GetString(buf);
Console.Error.WriteLine($"read a buffer of size {len}, content: <<{s}>>");
return JsonSerializer.Deserialize(buf, CommandSerializerContext.Default.Command);
}
}
98 changes: 23 additions & 75 deletions src/StructuredLogViewer.Wasi.Engine/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
var sender = new Sender(stdOut);

sender.SendReady();
using var stdIn = Console.OpenStandardInput();
var parser = new CommandParser(stdIn);

var build = BinaryLog.ReadBuild(binlogPath);
BuildAnalyzer.AnalyzeBuild(build);

Expand All @@ -37,54 +40,39 @@
bool done = false;
do
{
if (!TryParseCommand(out Command command, out int requestId))
{
throw new InvalidOperationException("Could not parse command");
}
switch (command)
var command = parser.ParseCommand();
Console.Error.WriteLine($"parsed a command of type {command.GetType().Name}");
switch (command.Type)
{
case Command.Quit:
case CommandType.Quit:
done = true;
break;
case Command.Root:
SendNode(sender, nodeIds, build, requestId);
case CommandType.Root:
SendNode(sender, nodeIds, build, command.RequestId);
break;
case Command.Node:
if (int.TryParse(Console.ReadLine(), out var requestedNodeId))
case CommandType.Node:
var nodeCommand = command as NodeCommand;
if (nodeIds.FindNodeWithId(nodeCommand.NodeId, out BaseNode node))
{
if (nodeIds.FindNodeWithId(requestedNodeId, out BaseNode node))
{
nodeCollector.MarkExplored(node);
SendNode(sender, nodeIds, node, requestId);
break;
}
else
{
throw new InvalidOperationException("no node with requested id");
}
nodeCollector.MarkExplored(node);
SendNode(sender, nodeIds, node, nodeCommand.RequestId);
break;
}
else
{
throw new InvalidOperationException("can't parse node Id");
throw new InvalidOperationException("no node with requested id");
}
case Command.ManyNodes:
if (int.TryParse(Console.ReadLine(), out var requestedStartId) &&
int.TryParse(Console.ReadLine(), out var count))
case CommandType.ManyNodes:
var manyNodesCommand = command as ManyNodesCommand;
if (nodeIds.FindNodeWithId(manyNodesCommand.NodeId, out BaseNode start))
{
if (nodeIds.FindNodeWithId(requestedStartId, out BaseNode start))
{
BaseNode[] nodes = nodeCollector.CollectNodes(start, count);
SendManyNodes(sender, nodeIds, nodes, requestId);
break;
}
else
{
throw new InvalidOperationException("no start node with requested id");
}
BaseNode[] nodes = nodeCollector.CollectNodes(start, manyNodesCommand.Count);
SendManyNodes(sender, nodeIds, nodes, manyNodesCommand.RequestId);
break;
}
else
{
throw new InvalidOperationException("can't parse manyNodes id and count");
throw new InvalidOperationException("no start node with requested id");
}
default:
throw new UnreachableException("should not get here");
Expand Down Expand Up @@ -161,46 +149,6 @@ Node FormatNode(NodeMapper nodeIds, BaseNode node)
sender.SendNodes(msg);
}

bool
TryParseCommand(out Command command, out int requestId)
{
var requestIdStr = Console.ReadLine();
if (!int.TryParse(requestIdStr, out requestId))
{
command = default;
return false;
}
var cmd = Console.ReadLine();
switch (cmd)
{
case "quit":
command = Command.Quit;
return true;
case "root":
command = Command.Root;
return true;
case "node":
command = Command.Node;
return true;
case "manyNodes":
command = Command.ManyNodes;
return true;
default:
command = default;
return false;
}
}


enum Command
{
None = 0,
Quit,
Root,
Node,
ManyNodes,
}

class NodeMapper
{
public readonly Dictionary<BaseNode, int> nodeToId = new();
Expand Down
52 changes: 28 additions & 24 deletions vscode/src/extension/web/MSBuildLogDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { SyncRequestDispatch } from '../../shared/sync-request';
import { polyfillStreams, jsonFromChunks, stringFromChunks } from './streaming';
import { loadWasm, WasmEngine, WasmState, WasmStateChangeEvent } from './wasm/engine';
import * as wasiWasm from '@vscode/wasm-wasi';
import { assertNever } from '../../shared/assert-never';

interface WasmToCodeMessage {
type: string;
Expand Down Expand Up @@ -65,7 +64,7 @@ type CodeToWasmCommand =
export class MSBuildLogDocument implements vscode.CustomDocument {
disposables: DisposableLike[];
readonly _requestDispatch: SyncRequestDispatch<WasmToCodeReply>;
constructor(readonly uri: Uri, readonly _engine: WasmEngine, readonly out: vscode.LogOutputChannel) {
constructor(readonly _pipeIn: wasiWasm.Writable, readonly uri: Uri, readonly _engine: WasmEngine, readonly out: vscode.LogOutputChannel) {
this.disposables = [];
this.disposables.push(this._engine);
this.disposables.push(this._requestDispatch = new SyncRequestDispatch<WasmToCodeReply>());
Expand All @@ -83,7 +82,7 @@ export class MSBuildLogDocument implements vscode.CustomDocument {
isLive(): boolean { return this._engine.isLive(); }

gotStdOut(value: unknown) {
this.out.info(`received from wasm process: ${value}`);
this.out.info(`received from wasm process: ${JSON.stringify(value)}`);
if (isWasmToCodeMessage(value)) {
switch (value.type) {
case 'ready':
Expand Down Expand Up @@ -113,26 +112,24 @@ export class MSBuildLogDocument implements vscode.CustomDocument {
get onStateChange(): vscode.Event<WasmStateChangeEvent> { return this._engine.onStateChange; }

async postCommand(c: CodeToWasmCommand): Promise<void> {
let requestId = c.requestId;
let command = c.command;
let extra: string = '';
switch (c.command) {
case 'root':
break;
case 'node':
extra = `${c.nodeId}\n`;
break;
case 'manyNodes':
extra = `${c.nodeId}\n${c.count}\n`;
break;
default:
assertNever(c);
break;
}
await this._engine.process.stdin?.write(`${requestId}\n${command}\n${extra}`, 'utf-8');
const json = JSON.stringify(c);
const encoder = new TextEncoder();
const jsonBytes = encoder.encode(json);
const len = jsonBytes.byteLength;
this.out?.info(`sending ${len} followed by <<${json}>>`);
const lenBuf = new ArrayBuffer(4);
const int32View = new Int32Array(lenBuf);
int32View[0] = len;
const int8View = new Uint8Array(lenBuf);
int8View[0]++;
int8View[1]++;
int8View[2]++;
int8View[3]++;
await this._pipeIn.write(int8View);
await this._pipeIn.write(jsonBytes);
await this._pipeIn.write('\n', 'utf-8');
}


async requestRoot(): Promise<WasmToCodeNodeReply> {
const [requestId, replyPromise] = this._requestDispatch.promiseReply<WasmToCodeNodeReply>();
this.out.info(`requested root id=${requestId}`);
Expand Down Expand Up @@ -172,9 +169,16 @@ export async function openMSBuildLogDocument(context: vscode.ExtensionContext, u
out.info(`opening msbuild log ${uri}`);
const wasm = await loadWasm();
await polyfillStreams();
//const memFS = await wasm.createMemoryFileSystem();
const rootFileSystem = await wasm.createRootFileSystem([
{ kind: 'workspaceFolder' }
{ kind: 'workspaceFolder' },
//{
// kind: 'memoryFileSystem',
// mountPoint: '/pipe',
// fileSystem: memFS
//},
]);
//const pipeIn = memFS.createWritable('./input', 'utf-8');
const pipeIn = wasm.createWritable();
const pipeOut = wasm.createReadable();
const pipeErr = wasm.createReadable();
Expand All @@ -185,7 +189,7 @@ export async function openMSBuildLogDocument(context: vscode.ExtensionContext, u
out: { 'kind': 'pipeOut', pipe: pipeOut },
err: { 'kind': 'pipeOut', pipe: pipeErr },
},
rootFileSystem
rootFileSystem,
};
const path = Uri.joinPath(context.extensionUri, 'dist', 'StructuredLogViewer.Wasi.Engine.wasm');
const moduleBytes = await vscode.workspace.fs.readFile(path);
Expand All @@ -194,6 +198,6 @@ export async function openMSBuildLogDocument(context: vscode.ExtensionContext, u
const process = await wasm.createProcess('StructuredLogViewer.Wasi.Engine', module, options);
const engine = new WasmEngine(process, out);
out.info('process created')
return new MSBuildLogDocument(uri, engine, out);
return new MSBuildLogDocument(pipeIn, uri, engine, out);
}

0 comments on commit 6707ed6

Please sign in to comment.