Skip to content

Commit 7e7e0d6

Browse files
ebrevdotensorflower-gardener
authored andcommitted
Add new QueueRunner optional argument: queue_closed_exception_types.
* This is a backwards compatible change. * Includes some extra helper functions in the tf.errors module. * Includes extension to the QueueRunner proto * Includes tests that QueueRunner.{from,to}_proto are backwards compatible. Change: 131791267
1 parent 4f7a434 commit 7e7e0d6

4 files changed

Lines changed: 100 additions & 6 deletions

File tree

tensorflow/core/protobuf/queue_runner.proto

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ option java_outer_classname = "QueueRunnerProtos";
66
option java_multiple_files = true;
77
option java_package = "org.tensorflow.framework";
88

9+
import "tensorflow/core/lib/core/error_codes.proto";
10+
911
// Protocol buffer representing a QueueRunner.
1012
message QueueRunnerDef {
1113
// Queue name.
@@ -19,4 +21,8 @@ message QueueRunnerDef {
1921

2022
// The operation to run to cancel the queue.
2123
string cancel_op_name = 4;
24+
25+
// A list of exception types considered to signal a safely closed queue
26+
// if raised during enqueue operations.
27+
repeated error.Code queue_closed_exception_types = 5;
2228
}

tensorflow/python/framework/errors.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,10 +428,21 @@ def __init__(self, node_def, op, message):
428428
DATA_LOSS: DataLossError,
429429
}
430430

431+
_EXCEPTION_CLASS_TO_CODE = dict((
432+
(class_, code) for (code, class_) in _CODE_TO_EXCEPTION_CLASS.items()))
433+
434+
435+
def exception_type_from_error_code(error_code):
436+
return _CODE_TO_EXCEPTION_CLASS[error_code]
437+
438+
439+
def error_code_from_exception_type(cls):
440+
return _EXCEPTION_CLASS_TO_CODE[cls]
441+
431442

432443
def _make_specific_exception(node_def, op, message, error_code):
433444
try:
434-
exc_type = _CODE_TO_EXCEPTION_CLASS[error_code]
445+
exc_type = exception_type_from_error_code(error_code)
435446
return exc_type(node_def, op, message)
436447
except KeyError:
437448
warnings.warn("Unknown error code: %d" % error_code)

tensorflow/python/training/queue_runner.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ class QueueRunner(object):
4444
"""
4545

4646
def __init__(self, queue=None, enqueue_ops=None, close_op=None,
47-
cancel_op=None, queue_runner_def=None):
47+
cancel_op=None, queue_closed_exception_types=None,
48+
queue_runner_def=None):
4849
"""Create a QueueRunner.
4950
5051
On construction the `QueueRunner` adds an op to close the queue. That op
@@ -61,6 +62,11 @@ def __init__(self, queue=None, enqueue_ops=None, close_op=None,
6162
enqueue_ops: List of enqueue ops to run in threads later.
6263
close_op: Op to close the queue. Pending enqueue ops are preserved.
6364
cancel_op: Op to close the queue and cancel pending enqueue ops.
65+
queue_closed_exception_types: Optional tuple of Exception types that
66+
indicate that the queue has been closed when raised during an enqueue
67+
operation. Defaults to `(tf.errors.OutOfRangeError,)`. Another common
68+
case includes `(tf.errors.OutOfRangeError, tf.errors.CancelledError)`,
69+
when some of the enqueue ops may dequeue from other Queues.
6470
queue_runner_def: Optional `QueueRunnerDef` protocol buffer. If specified,
6571
recreates the QueueRunner from its contents. `queue_runner_def` and the
6672
other arguments are mutually exclusive.
@@ -75,34 +81,50 @@ def __init__(self, queue=None, enqueue_ops=None, close_op=None,
7581
raise ValueError("queue_runner_def and queue are mutually exclusive.")
7682
self._init_from_proto(queue_runner_def)
7783
else:
78-
self._init_from_args(queue=queue, enqueue_ops=enqueue_ops,
79-
close_op=close_op, cancel_op=cancel_op)
84+
self._init_from_args(
85+
queue=queue, enqueue_ops=enqueue_ops,
86+
close_op=close_op, cancel_op=cancel_op,
87+
queue_closed_exception_types=queue_closed_exception_types)
8088
# Protect the count of runs to wait for.
8189
self._lock = threading.Lock()
8290
self._runs = 0
8391
# List of exceptions raised by the running threads.
8492
self._exceptions_raised = []
8593

8694
def _init_from_args(self, queue=None, enqueue_ops=None, close_op=None,
87-
cancel_op=None):
95+
cancel_op=None, queue_closed_exception_types=None):
8896
"""Create a QueueRunner from arguments.
8997
9098
Args:
9199
queue: A `Queue`.
92100
enqueue_ops: List of enqueue ops to run in threads later.
93101
close_op: Op to close the queue. Pending enqueue ops are preserved.
94102
cancel_op: Op to close the queue and cancel pending enqueue ops.
103+
queue_closed_exception_types: Tuple of exception types, which indicate
104+
the queue has been safely closed.
95105
96106
Raises:
97107
ValueError: If `queue` or `enqueue_ops` are not provided when not
98108
restoring from `queue_runner_def`.
109+
TypeError: If `queue_closed_exception_types` is provided, but is not
110+
a non-empty tuple of error types (subclasses of `tf.errors.OpError`).
99111
"""
100112
if not queue or not enqueue_ops:
101113
raise ValueError("Must provide queue and enqueue_ops.")
102114
self._queue = queue
103115
self._enqueue_ops = enqueue_ops
104116
self._close_op = close_op
105117
self._cancel_op = cancel_op
118+
if queue_closed_exception_types is not None:
119+
if (not isinstance(queue_closed_exception_types, tuple)
120+
or not queue_closed_exception_types
121+
or not all(issubclass(t, errors.OpError)
122+
for t in queue_closed_exception_types)):
123+
raise TypeError(
124+
"queue_closed_exception_types, when provided, "
125+
"must be a non-empty list of tf.error types, but saw: %s"
126+
% queue_closed_exception_types)
127+
self._queue_closed_exception_types = queue_closed_exception_types
106128
# Close when no more will be produced, but pending enqueues should be
107129
# preserved.
108130
if self._close_op is None:
@@ -111,6 +133,11 @@ def _init_from_args(self, queue=None, enqueue_ops=None, close_op=None,
111133
# to unblock everything so we can cleanly exit.
112134
if self._cancel_op is None:
113135
self._cancel_op = self._queue.close(cancel_pending_enqueues=True)
136+
if not self._queue_closed_exception_types:
137+
self._queue_closed_exception_types = (errors.OutOfRangeError,)
138+
else:
139+
self._queue_closed_exception_types = tuple(
140+
self._queue_closed_exception_types)
114141

115142
def _init_from_proto(self, queue_runner_def):
116143
"""Create a QueueRunner from `QueueRunnerDef`.
@@ -125,6 +152,13 @@ def _init_from_proto(self, queue_runner_def):
125152
in queue_runner_def.enqueue_op_name]
126153
self._close_op = g.as_graph_element(queue_runner_def.close_op_name)
127154
self._cancel_op = g.as_graph_element(queue_runner_def.cancel_op_name)
155+
self._queue_closed_exception_types = tuple(
156+
errors.exception_type_from_error_code(code)
157+
for code in queue_runner_def.queue_closed_exception_types)
158+
# Legacy support for old QueueRunnerDefs created before this field
159+
# was added.
160+
if not self._queue_closed_exception_types:
161+
self._queue_closed_exception_types = (errors.OutOfRangeError,)
128162

129163
@property
130164
def queue(self):
@@ -142,6 +176,10 @@ def close_op(self):
142176
def cancel_op(self):
143177
return self._cancel_op
144178

179+
@property
180+
def queue_closed_exception_types(self):
181+
return self._queue_closed_exception_types
182+
145183
@property
146184
def exceptions_raised(self):
147185
"""Exceptions raised but not handled by the `QueueRunner` threads.
@@ -185,7 +223,7 @@ def _run(self, sess, enqueue_op, coord=None):
185223
break
186224
try:
187225
sess.run(enqueue_op)
188-
except errors.OutOfRangeError:
226+
except self._queue_closed_exception_types: # pylint: disable=catching-non-exception
189227
# This exception indicates that a queue was closed.
190228
with self._lock:
191229
self._runs -= 1
@@ -290,6 +328,9 @@ def to_proto(self):
290328
queue_runner_def.enqueue_op_name.append(enqueue_op.name)
291329
queue_runner_def.close_op_name = self.close_op.name
292330
queue_runner_def.cancel_op_name = self.cancel_op.name
331+
queue_runner_def.queue_closed_exception_types.extend([
332+
errors.error_code_from_exception_type(cls)
333+
for cls in self._queue_closed_exception_types])
293334
return queue_runner_def
294335

295336
@staticmethod

tensorflow/python/training/queue_runner_test.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,5 +259,41 @@ def testStartQueueRunnersNonDefaultGraph(self):
259259
# The variable should be 3.
260260
self.assertEqual(3, var.eval())
261261

262+
def testQueueRunnerSerializationRoundTrip(self):
263+
graph = tf.Graph()
264+
with graph.as_default():
265+
queue = tf.FIFOQueue(10, tf.float32, name="queue")
266+
enqueue_op = tf.no_op(name="enqueue")
267+
close_op = tf.no_op(name="close")
268+
cancel_op = tf.no_op(name="cancel")
269+
qr0 = tf.train.QueueRunner(
270+
queue, [enqueue_op], close_op, cancel_op,
271+
queue_closed_exception_types=(
272+
tf.errors.OutOfRangeError, tf.errors.CancelledError))
273+
qr0_proto = tf.train.QueueRunner.to_proto(qr0)
274+
qr0_recon = tf.train.QueueRunner.from_proto(qr0_proto)
275+
self.assertEqual("queue", qr0_recon.queue.name)
276+
self.assertEqual(1, len(qr0_recon.enqueue_ops))
277+
self.assertEqual(enqueue_op, qr0_recon.enqueue_ops[0])
278+
self.assertEqual(close_op, qr0_recon.close_op)
279+
self.assertEqual(cancel_op, qr0_recon.cancel_op)
280+
self.assertEqual(
281+
(tf.errors.OutOfRangeError, tf.errors.CancelledError),
282+
qr0_recon.queue_closed_exception_types)
283+
284+
# Assert we reconstruct an OutOfRangeError for QueueRunners
285+
# created before QueueRunnerDef had a queue_closed_exception_types field.
286+
del qr0_proto.queue_closed_exception_types[:]
287+
qr0_legacy_recon = tf.train.QueueRunner.from_proto(qr0_proto)
288+
self.assertEqual("queue", qr0_legacy_recon.queue.name)
289+
self.assertEqual(1, len(qr0_legacy_recon.enqueue_ops))
290+
self.assertEqual(enqueue_op, qr0_legacy_recon.enqueue_ops[0])
291+
self.assertEqual(close_op, qr0_legacy_recon.close_op)
292+
self.assertEqual(cancel_op, qr0_legacy_recon.cancel_op)
293+
self.assertEqual(
294+
(tf.errors.OutOfRangeError,),
295+
qr0_legacy_recon.queue_closed_exception_types)
296+
297+
262298
if __name__ == "__main__":
263299
tf.test.main()

0 commit comments

Comments
 (0)