diff options
author | 2017-06-15 15:52:27 -0700 | |
---|---|---|
committer | 2017-06-15 15:56:58 -0700 | |
commit | c5436fc0c1047719e4f86942729db4dbc9c76113 (patch) | |
tree | 0a3c7faf4ac201ddee1d78c9241b397b6dba281d | |
parent | 6f2977185bab7dbc4d7970416e4a8f6bbb18c94c (diff) |
Extract request cancellation to a utility class
Summary:
The chart scaffold has some logic to ensure that old requests don't
stomp the data of new ones. This is a useful feature, and plugins will
want to continue to use it even after we destroy the chart-scaffold.
Thus, we extract it to a very simple utility class, here used by
chart-scaffold and in the future by plugins individually.
Test Plan:
Set a conditional breakpoint on the `tf-chart-scaffold` code that
triggers only when `result.cancelled` is true. Use extreme network
throttling to enter a situation where there _are_ multiple batches of
in-flight requests. Then, disable throttling so that all the requests
resolve; the breakpoint will then trigger.
PiperOrigin-RevId: 159166747
4 files changed, 84 insertions, 23 deletions
diff --git a/tensorflow/tensorboard/components/tf_backend/BUILD b/tensorflow/tensorboard/components/tf_backend/BUILD index 50fc267dc4..0136988e0e 100644 --- a/tensorflow/tensorboard/components/tf_backend/BUILD +++ b/tensorflow/tensorboard/components/tf_backend/BUILD @@ -10,6 +10,7 @@ ts_web_library( srcs = [ "backend.ts", "behavior.ts", + "canceller.ts", "requestManager.ts", "router.ts", "runsStore.ts", diff --git a/tensorflow/tensorboard/components/tf_backend/canceller.ts b/tensorflow/tensorboard/components/tf_backend/canceller.ts new file mode 100644 index 0000000000..a196f82258 --- /dev/null +++ b/tensorflow/tensorboard/components/tf_backend/canceller.ts @@ -0,0 +1,65 @@ +/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the 'License'); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an 'AS IS' BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +/** + * A class that allows marking promises as cancelled. + * + * This can be useful to, e.g., prevent old network requests from + * stomping new ones and writing bad data. + * + * Usage: + * + * const canceller = new Canceller(); + * let myPromise: Promise<Foo> = getPromise(); + * myPromise.then(canceller.cancellable(({value, cancelled} => { + * if (cancelled) { + * console.warn("Don't make promises you can't keep >:-{"); + * } + * console.log("Enjoy your value:", value); + * })); + * + * // If `myPromise` is resolved now, then `cancelled` will be `false`. + * canceller.cancelAll(); + * // If `myPromise` is resolved now, then `cancelled` will be `true`. + */ +export class Canceller { + /** + * How many times has `cancelAll` been called? + */ + private cancellationCount = 0; + + /** + * Create a cancellable task. This returns a new function that, when + * invoked, will pass its argument to the provided function as well as + * a `cancelled` argument. This argument will be `false` unless and + * until `cancelAll` is invoked after the creation of this task. + */ + public cancellable<T, U>(f: (result: {value: T, cancelled: boolean}) => U): + (T) => U { + const originalCancellationCount = this.cancellationCount; + return (value) => { + const cancelled = this.cancellationCount !== originalCancellationCount; + return f({value, cancelled}); + }; + } + + /** + * Mark all outstanding tasks as cancelled. Tasks not yet created will + * not be affected. + */ + public cancelAll(): void { + this.cancellationCount++; + } +} diff --git a/tensorflow/tensorboard/components/tf_backend/tf-backend.html b/tensorflow/tensorboard/components/tf_backend/tf-backend.html index c2a44b3b63..066647f5c2 100644 --- a/tensorflow/tensorboard/components/tf_backend/tf-backend.html +++ b/tensorflow/tensorboard/components/tf_backend/tf-backend.html @@ -26,3 +26,4 @@ limitations under the License. <script src="runsStore.js"></script> <script src="backend.js"></script> <script src="behavior.js"></script> +<script src="canceller.js"></script> diff --git a/tensorflow/tensorboard/components/tf_dashboard_common/tf-chart-scaffold.html b/tensorflow/tensorboard/components/tf_dashboard_common/tf-chart-scaffold.html index a39fb9462b..d337111363 100644 --- a/tensorflow/tensorboard/components/tf_dashboard_common/tf-chart-scaffold.html +++ b/tensorflow/tensorboard/components/tf_dashboard_common/tf-chart-scaffold.html @@ -56,6 +56,7 @@ plugin is required to implement two functions: </template> <script> "use strict"; + import {Canceller} from "../tf-backend/canceller"; Polymer({ is: "tf-chart-scaffold", @@ -67,14 +68,11 @@ plugin is required to implement two functions: type: Boolean, value: false }, - - // Storing the update ID of the previous request for data enables us to determine if a - // data response is outdated. We rely on an increasing ID instead of timestamp because - // successive updates often fire within the same millisecond. - _dataUpdateIdOfLastRequest: Number, - _nextAvailableDataUpdateId: { - type: Number, - value: 1, + _canceller: { + type: Object, + value: function() { + return new Canceller(); + }, }, }, observers: [ @@ -102,25 +100,21 @@ plugin is required to implement two functions: throw new Error('tf-chart-scaffold requires a tag.'); } - // TODO(chizeng): At this point, notify effective children that the previous data has been - // invalidated. For instance, the image dashboard may want to clear its images. Today, the - // chart scaffold only informs children when the new image URLs response finishes loading. - - const dataUpdateId = this._nextAvailableDataUpdateId++; - this._dataUpdateIdOfLastRequest = dataUpdateId; - + // TODO(chizeng): At this point, notify effective children that the + // previous data has been invalidated. For instance, the image + // dashboard may want to clear its images. Today, the chart + // scaffold only informs children when the new image URLs response + // finishes loading. + this._canceller.cancelAll(); // prevent stale data this.visibleSeries.forEach(function(name) { - this.dataProvider(this.tag, name).then(function(data) { - if (dataUpdateId != this._dataUpdateIdOfLastRequest) { - // This response is outdated. Ignore it. - // TODO(chizeng): Explore canceling an outdated request before we even receive its - // response. This involves creating hooks into the request manager and might introduce - // some complexity that may not be worth it; Tensorboard frankly does not seem - // bottlenecked by the network (It is often run in fast corp networks or locally.). + var promise = this.dataProvider(this.tag, name); + promise.then(this._canceller.cancellable(function(result) { + if (result.cancelled) { return; } + var data = result.value; this.chart().setSeriesData(name, data); - }.bind(this)); + }.bind(this))); }.bind(this)); }, _changeSeries: function() { |