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

new Channel API #20731

Merged
merged 22 commits into from
Jul 18, 2024
Merged

Conversation

allisonkarlitskaya
Copy link
Member

@allisonkarlitskaya allisonkarlitskaya commented Jul 9, 2024

This brings along a new approach to typing Channel which should make it easier to implement derived channel types.

Copy link
Member

@martinpitt martinpitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first 5 commits are unrelated/independent. Sending a separate review for these.

pkg/lib/cockpit/_internal/transport.js Outdated Show resolved Hide resolved
Copy link
Member

@martinpitt martinpitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks nice! I have a few nitpicks, but mostly questions.

pkg/lib/cockpit/_internal/transport.d.ts Outdated Show resolved Hide resolved
pkg/lib/cockpit/fsinfo.ts Show resolved Hide resolved
pkg/lib/cockpit/channel.ts Show resolved Hide resolved
pkg/lib/cockpit/channel.ts Show resolved Hide resolved
this.#received[command] = control;
this.emit(command, control);
} else {
this.emit('control', control);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked a lot of the umpteen uncovered code complaints. Many are really unreachable "should not happen" code, and things like this are because we just ported over one channel type. This should get covered if we e.g. port the file channel to this (anything that supports a binary channel).

pkg/lib/cockpit/channel.ts Outdated Show resolved Hide resolved
pkg/lib/cockpit/channel.ts Outdated Show resolved Hide resolved
@allisonkarlitskaya allisonkarlitskaya force-pushed the channel-api branch 6 times, most recently from dea421a to a1fefe1 Compare July 16, 2024 02:00
@allisonkarlitskaya allisonkarlitskaya force-pushed the channel-api branch 5 times, most recently from 48bb540 to b68c2a5 Compare July 16, 2024 16:17
@allisonkarlitskaya
Copy link
Member Author

So the new warnings about 'send messages after close' are showing that we sometimes call .close() on closed channels...

I wonder if we should try to fix that at the use site (I guess fsinfo?) or try to be more tolerant in this particular case...

@allisonkarlitskaya allisonkarlitskaya force-pushed the channel-api branch 2 times, most recently from af30ac5 to 1263b0b Compare July 16, 2024 17:31
@martinpitt
Copy link
Member

I'm almost 💯 sure that this is a flake, but let's retry to compare.

Copy link
Member

@martinpitt martinpitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! There's only a bunch of questions and small stuff left. This grew so big, I hope the next iteration can be "it". 😁

pkg/lib/cockpit/_internal/transport.js Outdated Show resolved Hide resolved
pkg/lib/cockpit/_internal/parentwebsocket.ts Show resolved Hide resolved
pkg/lib/cockpit/_internal/transport.js Outdated Show resolved Hide resolved
pkg/base1/test-channel.ts Outdated Show resolved Hide resolved
pkg/base1/test-channel.ts Outdated Show resolved Hide resolved
pkg/base1/test-channel.ts Show resolved Hide resolved
pkg/base1/test-channel.ts Outdated Show resolved Hide resolved
pkg/lib/cockpit/channel.ts Show resolved Hide resolved
pkg/lib/cockpit/channel.ts Show resolved Hide resolved
@martinpitt
Copy link
Member

Hmm, these unit tests hanging on exactly all Debian/Ubuntus sounds too much for a coincidence 😢

@martinpitt
Copy link
Member

Wait -- these tests fail in exactly the same way in #20772 -- did we break main somehow?

@martinpitt
Copy link
Member

Last landed PR #20763 from 8 hours ago had green tests. This happens during .deb package build, so it can't access the internet even. So what on earth changed!?

@allisonkarlitskaya
Copy link
Member Author

Last landed PR #20763 from 8 hours ago had green tests. This happens during .deb package build, so it can't access the internet even. So what on earth changed!?

Could be a case of landing two PRs in parallel, each tested against their common ancestor, but never tested together until they hit main.

@allisonkarlitskaya
Copy link
Member Author

I'm gonna assume this has something to do with #20768... it's acting like the systemd mainloop isn't being properly registered.

@allisonkarlitskaya
Copy link
Member Author

...and indeed we landed #20768 without a full slate because I didn't think pytest ran during image builds.

@allisonkarlitskaya
Copy link
Member Author

#20773

 - disable the base no-use-before-define rule: it doesn't understand
   that TypeScript allows using types before they're defined.  Enable
   the TypeScript-specific rule for it for TypeScript files.

 - add an 'unused variables' config matching the default rules of the
   TypeScript compiler

Both of these rule stanzas were copied from the documentation of the
typescript-eslint plugin.  Links to those pages are provided in
comments, which are valid in the extended JSON syntax used for eslintrc.

Tweak our test/static-code rule for JSON to also exempt eslintrc from
our strict checking.
Having these in the "global" namespace alongside everything in
node_modules seems like a disaster waiting to happen.  Let's make a
`cockpit` dir and import them like `'cockpit/fsinfo'`, etc.
Stop using array_from_raw_string() and join_data() to create binary
frames and replace it with open-coded uses of `TextEncoder.encode()` and
`Uint8Array.set()`.  Drop the now-unused `array_from_raw_string()`.

Tweak the type check for binary messages to 'anything not a string'.
There are two APIs in pkg/lib/_internal/transport.js that aren't used in
any way outside of that file.  Don't "export" them.
Take the offer of the TypeScript compiler to "Convert function to an
ES2015 class".  This was a completely automated operation.
This class defines a single event, and the only code that connects to it
lives inside of the same file.  We can move this to our new API quite
easily.
Avoiding `function` means that `this` is the same thing throughout the
entire constructor function.

Change a couple of `window` event listeners while we're at it.
We no longer need this alias for `this` — since we only use arrows
inside of the constructor, we can access `this` directly.
Currently all of the functionality of this class is defined inside of
the constructor.  We want to start moving some of it out to separate
functions, so move all of the private state to instance private
properties.
These methods no longer depend on anything inside of the constructor()
function and can be moved out.  This is safe because there are no call
sites that use these functions as callbacks, so `this` will always be
correctly bound.

The methods formerly exported on the `self` object are turned into
public methods and the others are turned into privates with `#`.

The one tricky case here is `dispatch_data()` which is now a public
method, but also used as the WebSocket `.onmessage` handler.  In that
case we use an arrow to make sure `this` is properly provided:

    this.#ws.onmessage = event => this.dispatch_data(event);
Use a spread operator instead.  Also let's us avoid using `.apply()`.
We can safely assume by now that the browser always supports WebSocket.
This lets us eliminate a fallback path and clean up the logic a bit.
This essentially follows all the steps that we did with Transport, but
is small enough to do it in a single go.
Instead of having the transport code reach in to the global `cockpit`
object to set properties, just have the `init_callback` (already part of
the `cockpit` code) do it for us.
"in" filtering is used by the shell to inspect all incoming messages
for forwarding to the appropriate frame, but nothing uses "out" filters,
outside of a single test case which exists only to test the feature.

Removing this will substantially simplify the code and allow for further
cleanups, so let's drop it.
Now that we dropped support for filters, this only has a single
call site, and the types it has to deal with are somewhat simplified.
Merge it directly into that site, combining the logic for reading the
channel name with the logic for splitting out the payload part of the
message.
Instead of setting this to null by default, make it an empty list.  That
way we can avoid the check for initializing it, and can do a simple
iteration, unconditionally.
Instead of checking for truthiness, check that the value is actually a
string before assigning it to a variable that's used as a string.
This seems like a bit of a strange thing to do, but it will make it
easier to add annotations when porting to TypeScript.  Annotating object
literals requires providing a type separate from the value, whereas a
class allows the annotations to be given inline.
After all the prep work, this is a reasonably straight-forward exercise.
This is the API that we intend all future channel implementations
(including users outside of cockpit.js) to use.  Port fsinfo.
Copy link
Member

@martinpitt martinpitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebased after the pytest fix, cheers! This should go green now. 🚀

if (!transport_globals.outgoing_filters)
transport_globals.outgoing_filters = [];
transport_globals.outgoing_filters.push(callback);
console.error("'out' filters are no longer supported");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test.

@@ -33,7 +33,7 @@ if (typeof window.debugging === "undefined") {
get: function() { return window.sessionStorage.debugging || window.localStorage.debugging },
set: function(x) { window.sessionStorage.debugging = x }
});
} catch (e) { }
} catch { }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test.

@@ -7,7 +7,7 @@ function get_url_root(): string | null {
try {
// Sometimes this throws a SecurityError such as during testing
return window.localStorage.getItem('url-root');
} catch (e) {
} catch {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test.

readyState = 0;

// essentially signal handlers: these are assigned to from Transport
onopen(): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test.

onopen(): void {
}

onclose(): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test.

Comment on lines +261 to +264
if (this.#received.ready) {
resolve(this.#received.ready);
} else if (this.#received.close) {
reject(this.#received.close);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 4 added lines are not executed by any test.

Comment on lines +266 to +267
this.on('ready', resolve);
this.on('close', reject);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 2 added lines are not executed by any test.

Comment on lines +275 to +281
toString(): string {
const state =
(!this.id && 'waiting for transport') ||
(this.#received.close?.problem && `${this.id} error ${this.#received.close.problem}`) ||
(this.#received.close && `${this.id} closed`) ||
(this.#received.ready && `${this.id} opened`) ||
`${this.id} waiting for open`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 7 added lines are not executed by any test.

(this.#received.ready && `${this.id} opened`) ||
`${this.id} waiting for open`;

const host = this.options?.host || "localhost";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test.


const host = this.options?.host || "localhost";

return `[Channel ${state} -> ${this.options?.payload}@${host}]`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test.

@martinpitt martinpitt merged commit d4003a0 into cockpit-project:main Jul 18, 2024
76 of 77 checks passed
@martinpitt martinpitt deleted the channel-api branch July 18, 2024 04:33
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

Successfully merging this pull request may close these issues.

3 participants