Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StdIn / Stdout Example #1

Closed
stefc opened this issue Feb 19, 2019 · 10 comments
Closed

StdIn / Stdout Example #1

stefc opened this issue Feb 19, 2019 · 10 comments

Comments

@stefc
Copy link

stefc commented Feb 19, 2019

Beside the 'NamedPipeServerStream' I would love to see a jsonrpc sample like your's but based on StandardIO Streams.

Also I would like to see a
public static async Task Main(string[] args)

as the main entry point in your sample.

@gentledepp
Copy link

yea that would be very interesting and helpful indeed!

@gentledepp
Copy link

It seems there is a helpful sample in the vs code extensions documentation.
vs code uses JSON RPC over stdin/stdout to communicate with extensions that are not native nodejs:

see sample of language client

However, for some reason, it still does not work:
My app just calls itself and then crashes:
jsonrpc_stdinout

@AArnott - any hint would be greatly appreciated! :-)

@AArnott AArnott closed this as completed in 2babbdb Oct 5, 2020
@gentledepp
Copy link

OGM thank you so much! I would never have come to that solution!
It looks so simple once you know it ;-)

@AArnott
Copy link
Owner

AArnott commented Oct 5, 2020

@gentledepp It's hard for me to read your source code in your animated gif. What was it you were doing wrong? No bug jumped out at me.

@AArnott
Copy link
Owner

AArnott commented Oct 5, 2020

Also: is there any additional API or documentation that might have made this easier before I updated the sample?

@gentledepp
Copy link

I do not know. I'll attach the minimized source code that I came up with. After my sample was not working, I tried to copy yours step by step.

  • My app (even though I cannot see any difference) does not work.
    jsonrpc_stdinout_err

  • Yours works like a charm...

I simply do not see what is going wrong there... it seems though, like my "worker.exe" somehow is not initialized as I cannot seem to attach to it and resolve breakpoints (CTRL+ALT+P)
ConsoleStdin.zip

Maybe you can spot the difference?
(Event he csproj files are the same...🙄)

Also: I would really love to have some basic documentation on

  • how the communication via stdin/stdout is different to pipes (what is half-duplex in this regard?)
  • why you are using a stream from Nerdbank and what does Splice do? (I will try to look this up of course, but for someone trying to get started with this library, I can assure you that browsing through another projects source code is not the best experience ;-)

@AArnott
Copy link
Owner

AArnott commented Oct 6, 2020

Your code was almost fine. You just had two extra lines:

        static async Task Main(string[] args)
        {
-           Console.WriteLine("Worker starting up");
-           Console.WriteLine($"Startup args: {string.Join(" ", args)}");

            var stream = FullDuplexStream.Splice(Console.OpenStandardInput(),
                Console.OpenStandardOutput());
            var server = new MyJsonRpcServer();
            using var jsonRpc = JsonRpc.Attach(stream, server);

            await jsonRpc.Completion;
        }

You were polluting the JSON-RPC stream coming out of STDOUT of your console application. Notice in my application I use Console.Error.WriteLine everywhere? That keeps the STDOUT stream clean for JSON-RPC use. Once I removed the above two lines, your code works fine too.

how the communication via stdin/stdout is different to pipes (what is half-duplex in this regard?)

StreamJsonRpc works over any .NET Stream. Half vs. full duplex is a distinction I incorrectly used in the past. If you see documentation that mentions half-duplex please point me to it so I can correct it. (Full) Duplex means the stream is bidirectional. Simplex (which I used to incorrectly call half duplex) means a stream that goes only one direction.
In StreamJsonRpc you can either supply one duplex stream or two simplex streams.
STDIN and STDOUT are individually simplex streams while a pipe is a duplex stream. But they are essentially the same in other respects.
But as you now know in your own code, while using stdin/stdout is brain-dead simple, since these streams are so easy for anyone in the process to get, it can be hard to keep them 'clean' since anyone can call Console.WriteLine for logging or otherwise. I think that's why some folks go with named pipes instead -- the handles can be kept private.

why you are using a stream from Nerdbank and what does Splice do?

Splice takes two simplex streams and creates one duplex stream. It isn't necessary though -- you could have called the JsonRpc constructor and passed stdin/stdout directly.
Nerdbank.Streams is a library that I also maintain that contains a bunch of generally useful Stream APIs, adapters, etc. StreamJsonRpc depends on it for several features.

@gentledepp
Copy link

Thanks man! That is great information!
Now everything makes sense!

Only one question remains, though:

  • What if I wanted to log some output in my client console app before starting the server app?
  • What if I wanted to ask for user input (as some parameter was not supplied in the main args)?
    Can I just somehow flush stdout/stdin in that case?

I am generally more interested in the stdin/stdout approach as it is the one taken by vs code to communicate with its extensions.
I am playing around with writing some .net core module that needs to be integrated as an extension, but is not a real language server. (i.e. it produces some preview output)

So my suggestions:

  1. incorporate the example you gave me as a warning/side note in your documentation

Your code was almost fine. You just had two extra lines:

        static async Task Main(string[] args)
        {
-           Console.WriteLine("Worker starting up");
-           Console.WriteLine($"Startup args: {string.Join(" ", args)}");

            var stream = FullDuplexStream.Splice(Console.OpenStandardInput(),
                Console.OpenStandardOutput());
            var server = new MyJsonRpcServer();
            using var jsonRpc = JsonRpc.Attach(stream, server);

            await jsonRpc.Completion;
        }

You were polluting the JSON-RPC stream coming out of STDOUT of your console application. Notice in my application I use Console.Error.WriteLine everywhere? That keeps the STDOUT stream clean for JSON-RPC use. Once I removed the above two lines, your code works fine too.

  1. update your stdin sample to not use Nerdbank.Streams as it will almost certainly confuse newcomers

  2. update the documentation to remove "half-duplex" and rather elaborate on the "two simplex streams" as you did for me.
    Also, please include the warning that one could easily pollute the stdin/stdout streams for logging purposes!
    image

  3. Can I buy you a beer? 🍺

@AArnott
Copy link
Owner

AArnott commented Oct 6, 2020

What if I wanted to log some output in my client console app before starting the server app?

You can do what I did, and use Console.Error as I already explained.

What if I wanted to ask for user input (as some parameter was not supplied in the main args)?

Remember you spawned the child process and redirected its stdin/stdout. So the child process cannot ask the user anything via the console anyway. It's effectively an invisible process. It could pop a WPF dialog if you really wanted, but generally if you have something that needs to interact with the user, I would avoid using the stdio approach streams for other business.

Can I just somehow flush stdout/stdin in that case?

These streams auto-flush. But if the parent process wants to ignore the first few lines of emitted text for purposes of JSON-RPC, that's perfectly fine. Do whatever you need to with the streams before attaching JSON-RPC. But be aware that if you use APIs like Console.ReadLine that may buffer up more bytes than just the line it read as it searches for line endings, so switching from that to passing BaseStream to JsonRpc may end up giving it partial data. It's a painful road and not one that I'll have time to support you on.

I am generally more interested in the stdin/stdout approach as it is the one taken by vs code to communicate with its extensions

I think that's an over-simplification. Most VS Code extensions don't have to communicate like this at all. They're just activated in another process and they're invoked via an RPC mechanism they know nothing about.
If you're thinking about LSP, I believe they can arrange any of several options for RPC including pipes, stdin/stdout, and perhaps others.

incorporate the example you gave me as a warning/side note in your documentation

The updated StreamJsonRpc.Sample already does:

// Log to STDERR so as to not corrupt the STDOUT pipe that we may be using for JSON-RPC.

I'll think about adding it to the StreamJsonRpc docs itself too. I suppose as a potentially common case we can mention it.

update your stdin sample to not use Nerdbank.Streams as it will almost certainly confuse newcomers

I'll consider it, but it makes 1 line become at least three so it actually makes the sample more complex.

update the documentation to remove "half-duplex" and rather elaborate on the "two simplex streams" as you did for me.

Ah, thanks for the pointer to the docs. I'll definitely get that fixed.

Can I buy you a beer? 🍺

Thanks! I don't drink, but I do accept donations. :)
GitHub sponsors or digital currency

@AArnott
Copy link
Owner

AArnott commented Oct 6, 2020

See microsoft/vs-streamjsonrpc#574

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants