Skip to content

Commit f3a7e06

Browse files
docs: DOC-1310: improve listener documentation (#7997)
Co-authored-by: rbasralian <rbasralian@gmail.com>
1 parent 6298dcf commit f3a7e06

13 files changed

Lines changed: 82 additions & 76 deletions

docs/groovy/how-to-guides/table-listeners-groovy.md

Lines changed: 67 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,15 @@ A [`MergedListener`](/core/javadoc/io/deephaven/engine/table/impl/MergedListener
5050
- `listenerDescription`: A String description of the listener.
5151
- `result`: a result table that uses the listener's tables as sources. Can be null.
5252

53-
The following example listens to two time tables, one ticking every two seconds and the other ticking every three seconds. [`getUpdate`](https://deephaven.io/core/javadoc/io/deephaven/engine/table/impl/ListenerRecorder.html#getUpdate()) returns a [`TableUpdate`](/core/javadoc/io/deephaven/engine/table/TableUpdate.html) object for each [`ListenerRecorder`](/core/javadoc/io/deephaven/engine/table/impl/ListenerRecorder.html), and the [`MergedListener`](/core/javadoc/io/deephaven/engine/table/impl/MergedListener.html)'s `process` function is overwritten to print updates if they have been received and to do nothing otherwise.
53+
The following example listens to two time tables, one ticking every second and the other ticking every two seconds. [`getUpdate`](https://deephaven.io/core/javadoc/io/deephaven/engine/table/impl/ListenerRecorder.html#getUpdate()) returns a [`TableUpdate`](/core/javadoc/io/deephaven/engine/table/TableUpdate.html) object for each [`ListenerRecorder`](/core/javadoc/io/deephaven/engine/table/impl/ListenerRecorder.html), and the [`MergedListener`](/core/javadoc/io/deephaven/engine/table/impl/MergedListener.html)'s `process` function is overwritten to print updates if they have been received and to do nothing otherwise.
5454

5555
```groovy ticking-table order=null reset
5656
import io.deephaven.engine.table.TableUpdate
57+
import io.deephaven.engine.table.ColumnSource
5758
import io.deephaven.engine.table.impl.ListenerRecorder
5859
import io.deephaven.engine.table.impl.MergedListener
60+
import io.deephaven.engine.rowset.RowSet
61+
import java.time.Instant
5962
6063
t1 = timeTable("PT1S").update("X=i").tail(5)
6164
t2 = timeTable("PT2S").update("X=i*2").tail(5)
@@ -69,35 +72,35 @@ h = new MergedListener([recorder1, recorder2], [], "Description", null) {
6972
@Override
7073
void process() {
7174
counter++
72-
tu1 = recorder1.getUpdate()
73-
tu2 = recorder2.getUpdate()
75+
TableUpdate tu1 = recorder1.getUpdate()
76+
TableUpdate tu2 = recorder2.getUpdate()
7477
75-
colSrc1 = t1.getColumnSource("Timestamp")
76-
colSrc2 = t1.getColumnSource("X")
77-
colSrc3 = t2.getColumnSource("Timestamp")
78-
colSrc4 = t2.getColumnSource("X")
78+
ColumnSource<Instant> colSrc1 = t1.getColumnSource("Timestamp", Instant.class)
79+
ColumnSource<Integer> colSrc2 = t1.getColumnSource("X", int.class)
80+
ColumnSource<Instant> colSrc3 = t2.getColumnSource("Timestamp", Instant.class)
81+
ColumnSource<Integer> colSrc4 = t2.getColumnSource("X", int.class)
7982
8083
if (tu1 != null) {
81-
it1 = tu1.added().iterator()
82-
while (it1.hasNext()) {
83-
col1Data = DateTimeUtils.epochNanosToInstant(colSrc1.getLong(it1.next()))
84-
col1Type = colSrc1.getType()
85-
col2Data = colSrc2.getInt(it1.next())
86-
col2Type = colSrc2.getType()
84+
RowSet.Iterator iter1 = tu1.added().iterator()
85+
while (iter1.hasNext()) {
86+
long rowKey = iter1.next()
87+
Instant col1Data = DateTimeUtils.epochNanosToInstant(colSrc1.getLong(rowKey))
88+
Class<?> col1Type = colSrc1.getType()
89+
int col2Data = colSrc2.getInt(rowKey)
90+
Class<?> col2Type = colSrc2.getType()
8791
println "t1 updates: {'Timestamp': [data=${col1Data}, ${col1Type}], 'X': [data=${col2Data}, ${col2Type}]"
8892
}
89-
it1 = null
9093
}
9194
if (tu2 != null) {
92-
it2 = tu2.added().iterator()
93-
while (it2.hasNext()) {
94-
col3Data = DateTimeUtils.epochNanosToInstant(colSrc3.getLong(it2.next()))
95-
col3Type = colSrc3.getType()
96-
col4Data = colSrc4.getInt(it2.next())
97-
col4Type = colSrc4.getType()
95+
RowSet.Iterator iter2 = tu2.added().iterator()
96+
while (iter2.hasNext()) {
97+
long rowKey = iter2.next()
98+
Instant col3Data = DateTimeUtils.epochNanosToInstant(colSrc3.getLong(rowKey))
99+
Class<?> col3Type = colSrc3.getType()
100+
int col4Data = colSrc4.getInt(rowKey)
101+
Class<?> col4Type = colSrc4.getType()
98102
println "t2 updates: {'Timestamp': [data=${col3Data}, ${col3Type}], 'X': [data=${col4Data}, ${col4Type}]"
99103
}
100-
it2 = null
101104
}
102105
}
103106
}
@@ -112,7 +115,7 @@ t2.addUpdateListener(recorder2)
112115
![`t1` and `t2` update while `handle` prints updates to the log](../assets/how-to/listener-merged.gif)
113116

114117
> [!IMPORTANT]
115-
> `updates` contains `null` values for any table that has not changed during the update cycle. These `null` values must be handled to avoid raising errors.
118+
> The `TableUpdate` returned by `getUpdate()` is `null` for any table that has not changed during the update cycle. These `null` values must be handled to avoid raising errors.
116119
117120
## Access table data
118121

@@ -124,34 +127,35 @@ The following methods return a RowSet of the added, removed, or modified data:
124127
- [`added`](https://deephaven.io/core/javadoc/io/deephaven/engine/table/TableUpdate.html#added()) - rows added during the current update cycle.
125128
- [`modified`](https://deephaven.io/core/javadoc/io/deephaven/engine/table/TableUpdate.html#modified()) - rows modified during the current update cycle.
126129
- [`removed`](https://deephaven.io/core/javadoc/io/deephaven/engine/table/TableUpdate.html#removed()) - rows removed during the current update cycle.
127-
- [`getModifiedPreShift`](https://deephaven.io/core/javadoc/io/deephaven/engine/table/TableUpdate.html#getModifiedPreShift()) - rows modified during the previous update cycle.
130+
- [`getModifiedPreShift`](https://deephaven.io/core/javadoc/io/deephaven/engine/table/TableUpdate.html#getModifiedPreShift()) - modified rows in their pre-shift row key positions (before any shifts were applied during this update cycle).
128131

129132
The following example listens to added rows during each update cycle. It prints the data as the listener receives it.
130133

131134
```groovy ticking-table order=null reset
132135
import io.deephaven.engine.table.TableUpdate
136+
import io.deephaven.engine.table.ColumnSource
133137
import io.deephaven.engine.table.impl.InstrumentedTableUpdateListenerAdapter
138+
import io.deephaven.engine.rowset.RowSet
139+
import java.time.Instant
134140
135141
t1 = timeTable("PT1S").update("X=100+i").tail(5)
136142
137143
h1 = new InstrumentedTableUpdateListenerAdapter(t1, false) {
138-
def counter = 0
139144
140145
@Override
141146
public void onUpdate(TableUpdate upstream) {
142-
it1 = upstream.added().iterator()
143-
colSrc1 = t1.getColumnSource("Timestamp")
144-
colSrc2 = t1.getColumnSource("X")
145-
146-
while (it1.hasNext()) {
147-
it = it1.next()
148-
col1data = DateTimeUtils.epochNanosToInstant(colSrc1.getLong(it))
149-
col1type = colSrc1.getType()
150-
col2data = colSrc2.getInt(it)
151-
col2type = colSrc2.getType()
147+
RowSet.Iterator iter = upstream.added().iterator()
148+
ColumnSource<Instant> colSrc1 = t1.getColumnSource("Timestamp", Instant.class)
149+
ColumnSource<Integer> colSrc2 = t1.getColumnSource("X", int.class)
150+
151+
while (iter.hasNext()) {
152+
long rowKey = iter.next()
153+
Instant col1data = DateTimeUtils.epochNanosToInstant(colSrc1.getLong(rowKey))
154+
Class<?> col1type = colSrc1.getType()
155+
int col2data = colSrc2.getInt(rowKey)
156+
Class<?> col2type = colSrc2.getType()
152157
println "{'Timestamp': [data=${col1data}, type=${col1type}], 'X': [data=${col2data}, type=${col2type}]}"
153158
}
154-
155159
}
156160
}
157161
@@ -164,33 +168,34 @@ The following example listens to modified rows during each update cycle. It uses
164168

165169
```groovy ticking-table order=null reset
166170
import io.deephaven.engine.table.TableUpdate
171+
import io.deephaven.engine.table.ColumnSource
167172
import io.deephaven.engine.table.impl.InstrumentedTableUpdateListenerAdapter
173+
import io.deephaven.engine.rowset.RowSet
168174
169175
t1 = timeTable("PT0.1s").update("X = i").lastBy()
170176
171-
h1 = new InstrumentedTableUpdateListenerAdapter("listener", t1, null) {
177+
h1 = new InstrumentedTableUpdateListenerAdapter("listener", t1, false) {
172178
@Override
173179
public void onUpdate(TableUpdate upstream) {
174-
prevModified = upstream.getModifiedPreShift()
175-
currModified = upstream.modified()
180+
RowSet prevModified = upstream.getModifiedPreShift()
181+
RowSet currModified = upstream.modified()
176182
if (prevModified.size() == 0) {
177183
println "No previous values"
178184
return
179185
}
180186
181-
xCol = t1.getColumnSource("X")
187+
ColumnSource<Integer> xCol = t1.getColumnSource("X", int.class)
182188
183-
prevIt = prevModified.iterator()
184-
currIt = currModified.iterator()
189+
RowSet.Iterator prevIt = prevModified.iterator()
190+
RowSet.Iterator currIt = currModified.iterator()
185191
186192
while (prevIt.hasNext() && currIt.hasNext()) {
187-
prevRowIdx = prevIt.next()
188-
currRowIdx = currIt.next()
189-
prev = xCol.getPrevInt(prevRowIdx)
190-
curr = xCol.getInt(currRowIdx)
193+
long prevRowIdx = prevIt.next()
194+
long currRowIdx = currIt.next()
195+
int prev = xCol.getPrevInt(prevRowIdx)
196+
int curr = xCol.getInt(currRowIdx)
191197
println "Change previous=${prev} current=${curr}"
192198
}
193-
194199
}
195200
}
196201
@@ -248,9 +253,12 @@ ExecutionContext.getContext().getUpdateGraph().exclusiveLock().doLocked(() -> {
248253

249254
## Error handling
250255

251-
Use a try-catch block within the `onUpdate` call to define and handle errors when using table listeners.
256+
If a listener encounters an error, it should throw an exception rather than catching and suppressing it. When a listener throws an exception:
257+
258+
- In a **Persistent Query**, the exception crashes the query — which is the correct behavior, as it alerts you that something is wrong.
259+
- In a **Code Studio**, the error appears in the logs.
252260

253-
Consider an example where a listener raises an error if it receives a value greater than 10. A try-catch block prints a message about the error to the console.
261+
The following example throws a `RuntimeException` if it receives a value greater than 9. The exception propagates and causes the listener to fail, which is the expected behavior for a broken listener.
254262

255263
```groovy ticking-table order=null reset
256264
import io.deephaven.engine.table.TableUpdate
@@ -267,25 +275,21 @@ listener = new InstrumentedTableUpdateListenerAdapter("listener", source, false)
267275
268276
@Override
269277
void onUpdate(TableUpdate upstream) {
270-
try {
271-
def added = upstream.added()
272-
273-
if (added == null || added.isEmpty()) {
274-
return
275-
}
276-
if (added.any{element -> element > 9}) {
277-
throw new Exception("Number exceeds 9")
278-
}
279-
} catch (Exception e) {
280-
println "${e}"
278+
def added = upstream.added()
279+
280+
if (added == null || added.isEmpty()) {
281+
return
282+
}
283+
if (added.any{element -> element > 9}) {
284+
throw new RuntimeException("Value exceeds 9")
281285
}
282286
}
283287
}
284288
285289
source.addUpdateListener(listener)
286290
```
287291

288-
![`source` updates while `listener` prints error messages](../assets/how-to/listener-error.gif)
292+
![`source` updates while `listener` throws an exception when a value exceeds 9](../assets/how-to/listener-error.gif)
289293

290294
## Reduce data volumes
291295

@@ -322,7 +326,7 @@ A table listener can listen to data that existed before the listener was registe
322326

323327
To make a listener listen to previously existing data, set the `replayInitialImage` parameter to `true` when calling [`addUpdateListener`](https://deephaven.io/core/javadoc/io/deephaven/engine/table/impl/TableAdapter.html#addUpdateListener(io.deephaven.engine.table.ShiftObliviousListener,boolean)).
324328

325-
The following example registers two listeners with a time table a few seconds after it's created. Only the one that sets `replayInitialImage` to `True` receives data when it's first registered.
329+
The following example registers two listeners with a time table a few seconds after it's created. Only the one that sets `replayInitialImage` to `true` receives data when it's first registered.
326330

327331
```groovy ticking-table order=null reset
328332
import io.deephaven.engine.table.TableUpdate
@@ -348,7 +352,7 @@ source.removeUpdateListener(listener)
348352
source.addUpdateListener(listener, true)
349353
```
350354

351-
![`handle_replay` recieves all the data the table started with, while `handle_no_replay` only receives the updates after it was registered](../assets/how-to/listener-replay.gif)
355+
![`handle_replay` receives all the data the table started with, while `handle_no_replay` only receives the updates after it was registered](../assets/how-to/listener-replay.gif)
352356

353357
## Dependent tables
354358

@@ -439,5 +443,5 @@ source.addUpdateListener(listener)
439443
## Related documentation
440444

441445
- [`timeTable`](../reference/table-operations/create/timeTable.md)
442-
- [TableUpdate](/core/pydoc/code/deephaven.table_listener.html#deephaven.table_listener.TableUpdate)
446+
- [`TableUpdate`](/core/javadoc/io/deephaven/engine/table/TableUpdate.html)
443447
- [Table](/core/javadoc/io/deephaven/engine/table/Table.html)

docs/groovy/snapshots/0eb1a3b79108a643f5b29442466c2694.json

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"file":"how-to-guides/table-listeners-groovy.md","objects":{"source":{"type":"Table","data":{"columns":[{"name":"X","type":"double"}],"rows":[]}}}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"file":"how-to-guides/table-listeners-groovy.md","objects":{"t1":{"type":"Table","data":{"columns":[{"name":"Timestamp","type":"java.time.Instant"},{"name":"X","type":"int"}],"rows":[]}}}}

docs/groovy/snapshots/44e2c55371e6ecc37a70b95c32e30464.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/groovy/snapshots/4c5190b255a60f0346cdc18c703f8a93.json

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"file":"how-to-guides/table-listeners-groovy.md","objects":{"t1":{"type":"Table","data":{"columns":[{"name":"Timestamp","type":"java.time.Instant"},{"name":"X","type":"int"}],"rows":[]}}}}

docs/groovy/snapshots/ed78ce937c1c72350d8b525c0c1efe99.json

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"file":"how-to-guides/table-listeners-groovy.md","objects":{"t1":{"type":"Table","data":{"columns":[{"name":"Timestamp","type":"java.time.Instant"},{"name":"X","type":"int"}],"rows":[]}},"t2":{"type":"Table","data":{"columns":[{"name":"Timestamp","type":"java.time.Instant"},{"name":"X","type":"int"}],"rows":[]}}}}

0 commit comments

Comments
 (0)