@@ -28,6 +28,7 @@ import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient
28
28
import software .amazon .awssdk .services .dynamodb .model .AttributeValue
29
29
import software .amazon .awssdk .services .dynamodb .model .QueryRequest
30
30
import software .amazon .awssdk .services .dynamodb .model .QueryResponse
31
+ import scala .concurrent .Promise
31
32
32
33
/**
33
34
* INTERNAL API
@@ -42,6 +43,38 @@ import software.amazon.awssdk.services.dynamodb.model.QueryResponse
42
43
private val serialization = SerializationExtension (system)
43
44
private val fallbackStoreProvider = FallbackStoreProvider (system)
44
45
46
+ private val _backtrackingBreadcrumbSerId = Promise [Int ]()
47
+ def backtrackingBreadcrumbSerId (): Int = {
48
+ _backtrackingBreadcrumbSerId.future.value match {
49
+ case Some (v) => v.get
50
+ case None =>
51
+ val serializerIds = serialization.bindings.iterator.map(_._2.identifier).toSet
52
+ // Use an unfolding iterator to find a serializer ID that's not bound. There shouldn't ever be more
53
+ // than a few thousand serializer IDs bound (and the docs suggest real serializers should only have
54
+ // positive serializer IDs ("couple of billion")), so this shouldn't have to iterate far.
55
+ //
56
+ // Ranges that aren't indexable by an Int (viz. have more than Int.MaxValue elements) are surprisingly
57
+ // restrictive, thus the unfold
58
+ Iterator
59
+ .unfold(Int .MinValue ) { s =>
60
+ if (s != Int .MaxValue ) Some (s -> (s + 1 ))
61
+ else None
62
+ }
63
+ // stop fast if some other thread found this before we do
64
+ .find { i => ! serializerIds(i) || _backtrackingBreadcrumbSerId.isCompleted } match {
65
+ case None =>
66
+ // Over 4 billion serializers... really?
67
+ _backtrackingBreadcrumbSerId.tryFailure(new NoSuchElementException (" All serializer IDs used?" ))
68
+
69
+ case Some (id) =>
70
+ _backtrackingBreadcrumbSerId.trySuccess(id)
71
+ }
72
+ // first get is safe, we just ensured completion
73
+ // second get will throw if we exhausted serializers, but that's a danger we're prepared to face...
74
+ _backtrackingBreadcrumbSerId.future.value.get.get
75
+ }
76
+ }
77
+
45
78
private val bySliceProjectionExpression = {
46
79
import JournalAttributes ._
47
80
s " $Pid, $SeqNr, $Timestamp, $EventSerId, $EventSerManifest, $Tags, $BreadcrumbSerId, $BreadcrumbSerManifest"
@@ -182,6 +215,13 @@ import software.amazon.awssdk.services.dynamodb.model.QueryResponse
182
215
}
183
216
184
217
// implements BySliceQuery.Dao
218
+ // NB: if backtracking and an event that was saved to the fallback store is encountered,
219
+ // the payload will be None (as with any backtracking event) and the serId of the returned
220
+ // item will be one not used by any bound serializer.
221
+ //
222
+ // Without a payload, the serializer ID is kind of meaningless (and the events-by-slice
223
+ // queries in the read journal will ignore the serializer ID unless it is the filtered
224
+ // payload serializer).
185
225
override def itemsBySlice (
186
226
entityType : String ,
187
227
slice : Int ,
@@ -277,7 +317,8 @@ import software.amazon.awssdk.services.dynamodb.model.QueryResponse
277
317
writeTimestamp = getTimestamp(item),
278
318
readTimestamp = InstantFactory .now(),
279
319
payload = None , // lazy loaded for backtracking
280
- serId = item.get(EventSerId ).n().toInt,
320
+ serId =
321
+ if (item.containsKey(EventSerId )) item.get(EventSerId ).n().toInt else backtrackingBreadcrumbSerId(),
281
322
serManifest = " " ,
282
323
writerUuid = " " , // not need in this query
283
324
tags = if (item.containsKey(Tags )) item.get(Tags ).ss().asScala.toSet else Set .empty,
0 commit comments