aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar William Chargin <wchargin@google.com>2017-06-15 15:52:27 -0700
committerGravatar TensorFlower Gardener <gardener@tensorflow.org>2017-06-15 15:56:58 -0700
commitc5436fc0c1047719e4f86942729db4dbc9c76113 (patch)
tree0a3c7faf4ac201ddee1d78c9241b397b6dba281d
parent6f2977185bab7dbc4d7970416e4a8f6bbb18c94c (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
-rw-r--r--tensorflow/tensorboard/components/tf_backend/BUILD1
-rw-r--r--tensorflow/tensorboard/components/tf_backend/canceller.ts65
-rw-r--r--tensorflow/tensorboard/components/tf_backend/tf-backend.html1
-rw-r--r--tensorflow/tensorboard/components/tf_dashboard_common/tf-chart-scaffold.html40
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() {