Skip to content

Commit 6707ed6

Browse files
committed
XXX broken - we can't have nice things
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
1 parent 59921d0 commit 6707ed6

File tree

3 files changed

+155
-99
lines changed

3 files changed

+155
-99
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using System;
2+
using System.Data;
3+
using System.Drawing.Imaging;
4+
using System.IO;
5+
using System.Text;
6+
using System.Text.Json;
7+
using System.Text.Json.Serialization;
8+
9+
namespace StructuredLogViewer.Wasi.Engine;
10+
11+
[JsonPolymorphic(TypeDiscriminatorPropertyName = "command")]
12+
[JsonDerivedType(typeof(QuitCommand), typeDiscriminator: "quit")]
13+
[JsonDerivedType(typeof(RootCommand), typeDiscriminator: "root")]
14+
[JsonDerivedType(typeof(NodeCommand), typeDiscriminator: "node")]
15+
[JsonDerivedType(typeof(ManyNodesCommand), typeDiscriminator: "manyNodes")]
16+
internal abstract class Command
17+
{
18+
public int RequestId { get; set; }
19+
[JsonIgnore]
20+
public abstract CommandType Type { get; }
21+
}
22+
23+
enum CommandType
24+
{
25+
Quit,
26+
Root,
27+
Node,
28+
ManyNodes,
29+
}
30+
31+
32+
internal class QuitCommand : Command
33+
{
34+
public static QuitCommand Default = new();
35+
36+
public override CommandType Type => CommandType.Quit;
37+
}
38+
39+
internal class RootCommand : Command
40+
{
41+
public static RootCommand Default = new();
42+
43+
public override CommandType Type => CommandType.Root;
44+
}
45+
46+
internal class NodeCommand : Command
47+
{
48+
public int NodeId { get; set; }
49+
50+
public override CommandType Type => CommandType.Node;
51+
}
52+
53+
internal class ManyNodesCommand : Command
54+
{
55+
public int NodeId { get; set; }
56+
public int Count { get; set; }
57+
58+
public override CommandType Type => CommandType.ManyNodes;
59+
}
60+
61+
[JsonSourceGenerationOptions(WriteIndented = true, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
62+
[JsonSerializable(typeof(Command))]
63+
internal partial class CommandSerializerContext : JsonSerializerContext
64+
{
65+
}
66+
67+
// Stream format:
68+
// ([LENGTH][JSON])*
69+
// where LENGTH is an int32 in littleEndian order and indicates the size of the json payload.
70+
// the json payload is in utf8
71+
internal class CommandParser
72+
{
73+
74+
private readonly Stream _stream;
75+
76+
public CommandParser(Stream s)
77+
{
78+
_stream = s;
79+
}
80+
public Command ParseCommand()
81+
{
82+
Console.Error.WriteLine($"about to read an int32");
83+
Span<byte> lenBuf = stackalloc byte[4];
84+
_stream.ReadExactly(lenBuf);
85+
var len = (lenBuf[0] - 1) | (lenBuf[1] - 1) << 8 | (lenBuf[2] - 1) << 16 | (lenBuf[3] - 1) << 24;
86+
Console.Error.WriteLine($"read an int32 value: {len}");
87+
var buf = new byte[len];
88+
var bytesRead = 0;
89+
while (bytesRead < len)
90+
{
91+
var i = _stream.ReadByte();
92+
if (i < 0)
93+
{
94+
Console.Error.WriteLine($"wanted {len} bytes but got end of stream after {bytesRead}");
95+
throw new IOException("end of stream while wanted more bytes");
96+
}
97+
buf[bytesRead++] = (byte)i;
98+
}
99+
Console.Error.WriteLine($"Wanted {len} bytes, got {bytesRead}");
100+
var s = Encoding.UTF8.GetString(buf);
101+
Console.Error.WriteLine($"read a buffer of size {len}, content: <<{s}>>");
102+
return JsonSerializer.Deserialize(buf, CommandSerializerContext.Default.Command);
103+
}
104+
}

src/StructuredLogViewer.Wasi.Engine/Program.cs

+23-75
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
var sender = new Sender(stdOut);
3030

3131
sender.SendReady();
32+
using var stdIn = Console.OpenStandardInput();
33+
var parser = new CommandParser(stdIn);
34+
3235
var build = BinaryLog.ReadBuild(binlogPath);
3336
BuildAnalyzer.AnalyzeBuild(build);
3437

@@ -37,54 +40,39 @@
3740
bool done = false;
3841
do
3942
{
40-
if (!TryParseCommand(out Command command, out int requestId))
41-
{
42-
throw new InvalidOperationException("Could not parse command");
43-
}
44-
switch (command)
43+
var command = parser.ParseCommand();
44+
Console.Error.WriteLine($"parsed a command of type {command.GetType().Name}");
45+
switch (command.Type)
4546
{
46-
case Command.Quit:
47+
case CommandType.Quit:
4748
done = true;
4849
break;
49-
case Command.Root:
50-
SendNode(sender, nodeIds, build, requestId);
50+
case CommandType.Root:
51+
SendNode(sender, nodeIds, build, command.RequestId);
5152
break;
52-
case Command.Node:
53-
if (int.TryParse(Console.ReadLine(), out var requestedNodeId))
53+
case CommandType.Node:
54+
var nodeCommand = command as NodeCommand;
55+
if (nodeIds.FindNodeWithId(nodeCommand.NodeId, out BaseNode node))
5456
{
55-
if (nodeIds.FindNodeWithId(requestedNodeId, out BaseNode node))
56-
{
57-
nodeCollector.MarkExplored(node);
58-
SendNode(sender, nodeIds, node, requestId);
59-
break;
60-
}
61-
else
62-
{
63-
throw new InvalidOperationException("no node with requested id");
64-
}
57+
nodeCollector.MarkExplored(node);
58+
SendNode(sender, nodeIds, node, nodeCommand.RequestId);
59+
break;
6560
}
6661
else
6762
{
68-
throw new InvalidOperationException("can't parse node Id");
63+
throw new InvalidOperationException("no node with requested id");
6964
}
70-
case Command.ManyNodes:
71-
if (int.TryParse(Console.ReadLine(), out var requestedStartId) &&
72-
int.TryParse(Console.ReadLine(), out var count))
65+
case CommandType.ManyNodes:
66+
var manyNodesCommand = command as ManyNodesCommand;
67+
if (nodeIds.FindNodeWithId(manyNodesCommand.NodeId, out BaseNode start))
7368
{
74-
if (nodeIds.FindNodeWithId(requestedStartId, out BaseNode start))
75-
{
76-
BaseNode[] nodes = nodeCollector.CollectNodes(start, count);
77-
SendManyNodes(sender, nodeIds, nodes, requestId);
78-
break;
79-
}
80-
else
81-
{
82-
throw new InvalidOperationException("no start node with requested id");
83-
}
69+
BaseNode[] nodes = nodeCollector.CollectNodes(start, manyNodesCommand.Count);
70+
SendManyNodes(sender, nodeIds, nodes, manyNodesCommand.RequestId);
71+
break;
8472
}
8573
else
8674
{
87-
throw new InvalidOperationException("can't parse manyNodes id and count");
75+
throw new InvalidOperationException("no start node with requested id");
8876
}
8977
default:
9078
throw new UnreachableException("should not get here");
@@ -161,46 +149,6 @@ Node FormatNode(NodeMapper nodeIds, BaseNode node)
161149
sender.SendNodes(msg);
162150
}
163151

164-
bool
165-
TryParseCommand(out Command command, out int requestId)
166-
{
167-
var requestIdStr = Console.ReadLine();
168-
if (!int.TryParse(requestIdStr, out requestId))
169-
{
170-
command = default;
171-
return false;
172-
}
173-
var cmd = Console.ReadLine();
174-
switch (cmd)
175-
{
176-
case "quit":
177-
command = Command.Quit;
178-
return true;
179-
case "root":
180-
command = Command.Root;
181-
return true;
182-
case "node":
183-
command = Command.Node;
184-
return true;
185-
case "manyNodes":
186-
command = Command.ManyNodes;
187-
return true;
188-
default:
189-
command = default;
190-
return false;
191-
}
192-
}
193-
194-
195-
enum Command
196-
{
197-
None = 0,
198-
Quit,
199-
Root,
200-
Node,
201-
ManyNodes,
202-
}
203-
204152
class NodeMapper
205153
{
206154
public readonly Dictionary<BaseNode, int> nodeToId = new();

vscode/src/extension/web/MSBuildLogDocument.ts

+28-24
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { SyncRequestDispatch } from '../../shared/sync-request';
99
import { polyfillStreams, jsonFromChunks, stringFromChunks } from './streaming';
1010
import { loadWasm, WasmEngine, WasmState, WasmStateChangeEvent } from './wasm/engine';
1111
import * as wasiWasm from '@vscode/wasm-wasi';
12-
import { assertNever } from '../../shared/assert-never';
1312

1413
interface WasmToCodeMessage {
1514
type: string;
@@ -65,7 +64,7 @@ type CodeToWasmCommand =
6564
export class MSBuildLogDocument implements vscode.CustomDocument {
6665
disposables: DisposableLike[];
6766
readonly _requestDispatch: SyncRequestDispatch<WasmToCodeReply>;
68-
constructor(readonly uri: Uri, readonly _engine: WasmEngine, readonly out: vscode.LogOutputChannel) {
67+
constructor(readonly _pipeIn: wasiWasm.Writable, readonly uri: Uri, readonly _engine: WasmEngine, readonly out: vscode.LogOutputChannel) {
6968
this.disposables = [];
7069
this.disposables.push(this._engine);
7170
this.disposables.push(this._requestDispatch = new SyncRequestDispatch<WasmToCodeReply>());
@@ -83,7 +82,7 @@ export class MSBuildLogDocument implements vscode.CustomDocument {
8382
isLive(): boolean { return this._engine.isLive(); }
8483

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

115114
async postCommand(c: CodeToWasmCommand): Promise<void> {
116-
let requestId = c.requestId;
117-
let command = c.command;
118-
let extra: string = '';
119-
switch (c.command) {
120-
case 'root':
121-
break;
122-
case 'node':
123-
extra = `${c.nodeId}\n`;
124-
break;
125-
case 'manyNodes':
126-
extra = `${c.nodeId}\n${c.count}\n`;
127-
break;
128-
default:
129-
assertNever(c);
130-
break;
131-
}
132-
await this._engine.process.stdin?.write(`${requestId}\n${command}\n${extra}`, 'utf-8');
115+
const json = JSON.stringify(c);
116+
const encoder = new TextEncoder();
117+
const jsonBytes = encoder.encode(json);
118+
const len = jsonBytes.byteLength;
119+
this.out?.info(`sending ${len} followed by <<${json}>>`);
120+
const lenBuf = new ArrayBuffer(4);
121+
const int32View = new Int32Array(lenBuf);
122+
int32View[0] = len;
123+
const int8View = new Uint8Array(lenBuf);
124+
int8View[0]++;
125+
int8View[1]++;
126+
int8View[2]++;
127+
int8View[3]++;
128+
await this._pipeIn.write(int8View);
129+
await this._pipeIn.write(jsonBytes);
130+
await this._pipeIn.write('\n', 'utf-8');
133131
}
134132

135-
136133
async requestRoot(): Promise<WasmToCodeNodeReply> {
137134
const [requestId, replyPromise] = this._requestDispatch.promiseReply<WasmToCodeNodeReply>();
138135
this.out.info(`requested root id=${requestId}`);
@@ -172,9 +169,16 @@ export async function openMSBuildLogDocument(context: vscode.ExtensionContext, u
172169
out.info(`opening msbuild log ${uri}`);
173170
const wasm = await loadWasm();
174171
await polyfillStreams();
172+
//const memFS = await wasm.createMemoryFileSystem();
175173
const rootFileSystem = await wasm.createRootFileSystem([
176-
{ kind: 'workspaceFolder' }
174+
{ kind: 'workspaceFolder' },
175+
//{
176+
// kind: 'memoryFileSystem',
177+
// mountPoint: '/pipe',
178+
// fileSystem: memFS
179+
//},
177180
]);
181+
//const pipeIn = memFS.createWritable('./input', 'utf-8');
178182
const pipeIn = wasm.createWritable();
179183
const pipeOut = wasm.createReadable();
180184
const pipeErr = wasm.createReadable();
@@ -185,7 +189,7 @@ export async function openMSBuildLogDocument(context: vscode.ExtensionContext, u
185189
out: { 'kind': 'pipeOut', pipe: pipeOut },
186190
err: { 'kind': 'pipeOut', pipe: pipeErr },
187191
},
188-
rootFileSystem
192+
rootFileSystem,
189193
};
190194
const path = Uri.joinPath(context.extensionUri, 'dist', 'StructuredLogViewer.Wasi.Engine.wasm');
191195
const moduleBytes = await vscode.workspace.fs.readFile(path);
@@ -194,6 +198,6 @@ export async function openMSBuildLogDocument(context: vscode.ExtensionContext, u
194198
const process = await wasm.createProcess('StructuredLogViewer.Wasi.Engine', module, options);
195199
const engine = new WasmEngine(process, out);
196200
out.info('process created')
197-
return new MSBuildLogDocument(uri, engine, out);
201+
return new MSBuildLogDocument(pipeIn, uri, engine, out);
198202
}
199203

0 commit comments

Comments
 (0)