Continues reading from unix domain socket

I am currently creating an extension and have to communicate with a daemon over an unix domain socket.
I used a Gio.SocketClient and implemented an object with the Gio.SocketConnectable interface and a Gio.SocketAddressEnumerator to connect to my unix domain socket.
After getting a Gio.UnixConnection from the client with the connect_async method and getting the Gio.OutputStream from the connections get_output_stream method I can write bytes to the socket.
The socket replies with an simple “OK” when a message was received and keeps the connection open.

Now I want to read from this connection, so I used the get_input_stream method and tried to use read_all_async method, but this yields an error: JS ERROR: Error: Unsupported type array for (out caller-allocates)
I’ve found a few old issues about the caller-allocates but nothing helpful.

I tried waiting in a loop for something to read with read_all but the blocking nature of this call halted my entire gnome-shell.

I’m searching for a solution to constantly read from my connection in the background and send out a signal and the message, when something gets send over the connection.

Anyone got some ideas or clues?
Thanks in advance.

The short answer to read_all_async() is that GJS/JavaScript does not support the mutable buffer type necessary for that function. Ideally it would be excluded from our documentation, but some other language bindings do support this.

However, it’s pretty likely that you don’t want to use a “read all” function at all, since that means “keep reading data until the remote end stops providing it or closes”. More likely what you want is a read loop:

function readLoop(inputStream, cancellable = null) {
    // If you're reading from a daemon, it seems likely you're outputting
    // line-by-line but either way GDataInputStream is a useful wrapper
    if (!(inputStream instanceof Gio.DataInputStream))
        inputStream = Gio.DataInputStream.new(inputStream);

    inputStream.read_line_async(
        GLib.PRIORITY_DEFAULT,
        cancellable,
        (stream, res) => {
            try {
                const data = stream.read_line_finish_utf8(res)[0];

                if (data === null) {
                    throw new Gio.IOErrorEnum({
                        message: 'End of stream',
                        code: Gio.IOErrorEnum.CONNECTION_CLOSED,
                    });
                }

               // Do something with your data here, then recurse
               readLoop(stream, cancellable);
            } catch (e) {
                logError(e);
            }
        }
    );
}

You could also skip the recursion and wrap that in Promise, looping inside an async function or something, but generally this the pattern I’d use. Passing a GCancellable will allow you to halt the operation at anytime (eg. when your extension is disabled).

I’ve tried your provided solution and it works like a charm, thank you.

I have a small consideration about the recursion: Won’t the maximum stack size be exceeded if a lot of lines get send over?

I’ve tried to wrap the reading in a Promise and loop, but then the gnome-shell procces hangs up again, draining my RAM until it gets killed.
The loop will spawn new async reads indefintly, until the RAM is drained.
How can this be fixed?
Wouldn’t a loop and sync reads block the gnome shell again?

No, the event loop will prevent this from happening. A gross over-simplification is something like this:

const queuedEvents = [{
  func: myCallback,
  data: 'callback data',
}];

let source = undefined;

while ((source = queuedEvents.shift()))
    source.func(source.data);

The main loop iterates the main context checking for GLib.Source's that are marked as ready to be executed, thus the stack trace will only lead back as far as the call to GLib.MainLoop.run().

You’d have to show some code for us to see what’s going on. With a simple Promise-wrapped operation, you can run it in a loop like so:

function readLineAsync(stream, cancellable = null) {
    return new Promise((resolve, reject) => {
        stream.read_line_async(
            GLib.PRIORITY_DEFAULT,
            cancellable,
            (_stream, res) => {
                try {
                    resolve(_stream.read_line_finish_utf8(res)[0]);
                } catch (e) {
                    reject(e);
                }
            }
        );
    });
}

async readLoop(stream) {
    try {
        let line = null;

        while ((line = await readLineAsync(stream)) !== null)
            log(line);

        // End of stream reached, no error
    } catch (e) {
        logError(e);
    }
}

Of course, if you put a recursive function in a loop you will definitely run into problems, so you’ll want to choose either a single-shot function in a loop or a function that recurses.