KEMBAR78
plugin: message channels for dynamic plugins by psybuzz · Pull Request #2703 · tensorflow/tensorboard · GitHub
Skip to content

Conversation

@psybuzz
Copy link
Contributor

@psybuzz psybuzz commented Sep 27, 2019

  • Motivation for features / changes

This iterates on the experimental code in plugin_util/ for communication with IFrames. This PR does NOT address the public API.

  • Technical description of changes

Using window.postMessage and window.addEventListener('message', ...) works, but
Having multiple concurrent IPCs (e.g. a frame cannot be a host and a guest, a host cannot have multiple guests) required today's idPrefix system. Random-number-generated idPrefixes are prepended to message ids to distinguish which frame it comes from, to resolve the appropriate responseWait.

This PR uses MessageChannels, instead of random-numbers, to establish a private channel between frames.

The IPC now requires an isReply: true to be present on reply messages. This allows a frame to distinguish a case where (it receives a foreign message with id=X) vs (it receives a reply for its own previous message with id=X).

The flow looks like

  • Guest frame sends a normal hostWindow.postMessage to bootstrap, transferring ownership of one of it's MessagePorts
  • Host frame establishes an IPC on the received port (no need to worry about stray a postMessage from an extension!)
  • Guest can sendMessage(), listen(), unlisten() as usual. Host can broadcast() on all established ports.

WANT_LGTM=all

@psybuzz psybuzz requested review from davidsoergel and stephanwlee and removed request for davidsoergel and stephanwlee September 27, 2019 16:33
@psybuzz psybuzz changed the title plugin: message channels for dynamic plugins [DRAFT] plugin: message channels for dynamic plugins Sep 27, 2019
@psybuzz psybuzz changed the title [DRAFT] plugin: message channels for dynamic plugins plugin: message channels for dynamic plugins Sep 27, 2019
@psybuzz
Copy link
Contributor Author

psybuzz commented Sep 27, 2019

Added tests, cr address. Ready for another look!

Copy link
Contributor

@stephanwlee stephanwlee left a comment

Choose a reason for hiding this comment

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

Thanks for making this module better! Also thanks for adding test cases I missed!

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this for readability of the module? Or does it have functional differences?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Functional difference, and the test I added covers it!

Basically, if a frame receives message {id: 0, ...}, it doesn't know whether it's the reply-message to something it previously sent, or whether it's a brand new incoming message from another frame.

In both cases, the origin is a foreign frame, but we need to distinguish that the former case should resolve the responseWait, while the latter should not.

Copy link
Contributor

Choose a reason for hiding this comment

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

consider using the fluent alternative.
expect(longTaskCb).to.have.been.calledOnce;

Copy link
Contributor

Choose a reason for hiding this comment

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

consider adding assertion on the payload. expect(longTaskCb).to.have.been.calledWith(...)

Copy link
Contributor Author

@psybuzz psybuzz Sep 28, 2019

Choose a reason for hiding this comment

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

Done x2
Yes, asserting the payload is a good idea

@psybuzz
Copy link
Contributor Author

psybuzz commented Sep 28, 2019

Comments addressed, ready for another look.

@davidsoergel
Copy link
Member

I really think we need more consensus on what plugin architecture we're aiming for before checking in code. The two open docs (Stephan's and Erik's) are a good start, as is the new weekly meeting. Let's use those venues, as well as perhaps the TB design review, to solidify our plans--and then return to this.

@stephanwlee
Copy link
Contributor

stephanwlee commented Sep 30, 2019

@davidsoergel would this draft PR (#2705) be what you are seeking? Just do note that this work is time sensitive.

@psybuzz
Copy link
Contributor Author

psybuzz commented Sep 30, 2019

@davidsoergel as per offline discussion, I've moved plugin_util/ under tensorboard/components/experiments/, to indicate that this is current work is unstable.

I opted to not put it at the root level (tensorboard/experiments/) because there happens to be another build target "plugin_util" at the root level BUILD that has nothing todo with components.

Would this be fine with you?

Copy link
Member

@davidsoergel davidsoergel left a comment

Choose a reason for hiding this comment

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

components/experimental is good, thanks.

I don't fully follow the logic re id collisions and isReply. It's fine given the experimental status, but I'm looking forward to whiteboarding/etc. to understand this once other dust settles. Thanks!

}
}

export const broadcast = _broadcast;
Copy link
Member

Choose a reason for hiding this comment

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

Is the purpose of this pattern (as opposed to just export function broadcast(...) {...}) to prevent monkey-patching? Just curious if I should adopt this generally.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The underscored version is just there to allow us to export it via es6 (which is used/imported by the test) and export it under a namespace, at the same time.

export const listen = _listen;
export const unlisten = _unlisten;

namespace tf_plugin {
Copy link
Member

Choose a reason for hiding this comment

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

Namespaces are disallowed by go/tsstyle (publicly: https://github.com/google/gts/blob/83cb107010f7a956bb008291066304a9f26a0f34/tslint-rules.json#L43). TensorBoard contains a fair number of them anyway, but let's not add more if possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. @stephanwlee, could you weigh in on this?

There does seem to be alot of existing namespaces, ever since this PR, but I think the Angular ng-tensorboard/ folder avoids this.

Copy link
Contributor

Choose a reason for hiding this comment

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

  1. plugin-host (at least until the tf_ng_tensorboard work is finished) will be loaded inside Polymer context which uses the namespace
  2. (in anticipation of inevitable question) in ideal world, we want to share base code between host and guest impl except it is super hard to make ts_library build rule work with tf_web_library. The latter is used in mainly for polymer based TensorBoard. For now, since the experimental API is only used in context of Polymer anyways, I decided to just deprecate the [es]module style and use namespace everywhere as v0.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the explanation. It's unfortunate that Polymer code depends on these files. I'll land as-is for now, but it seems we all agree that 'namespaces' are not ideal, and should be avoided if possible.

@psybuzz psybuzz merged commit 842b881 into tensorflow:master Oct 1, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants