@@ -8,12 +8,12 @@ use aws_config::{BehaviorVersion, Region, SdkConfig};
8
8
use aws_credential_types:: Credentials ;
9
9
use aws_sdk_dynamodb:: {
10
10
config:: { ProvideCredentials , SharedCredentialsProvider } ,
11
- operation:: {
12
- batch_get_item:: BatchGetItemOutput , batch_write_item:: BatchWriteItemOutput ,
13
- get_item:: GetItemOutput ,
14
- } ,
11
+ operation:: { batch_get_item:: BatchGetItemOutput , batch_write_item:: BatchWriteItemOutput } ,
15
12
primitives:: Blob ,
16
- types:: { AttributeValue , DeleteRequest , KeysAndAttributes , PutRequest , WriteRequest } ,
13
+ types:: {
14
+ AttributeValue , DeleteRequest , Get , KeysAndAttributes , PutRequest , TransactGetItem ,
15
+ TransactWriteItem , Update , WriteRequest ,
16
+ } ,
17
17
Client ,
18
18
} ;
19
19
use spin_core:: async_trait;
@@ -353,21 +353,34 @@ impl Store for AwsDynamoStore {
353
353
#[ async_trait]
354
354
impl Cas for CompareAndSwap {
355
355
async fn current ( & self ) -> Result < Option < Vec < u8 > > , Error > {
356
- let GetItemOutput {
357
- item : Some ( mut current_item) ,
358
- ..
359
- } = self
356
+ // TransactGetItems fails if concurrent writes are in progress on an item
357
+ let output = self
360
358
. client
361
- . get_item ( )
362
- . table_name ( self . table . as_str ( ) )
363
- . key (
364
- PK ,
365
- aws_sdk_dynamodb:: types:: AttributeValue :: S ( self . key . clone ( ) ) ,
359
+ . transact_get_items ( )
360
+ . transact_items (
361
+ TransactGetItem :: builder ( )
362
+ . get (
363
+ Get :: builder ( )
364
+ . table_name ( self . table . as_str ( ) )
365
+ . key (
366
+ PK ,
367
+ aws_sdk_dynamodb:: types:: AttributeValue :: S ( self . key . clone ( ) ) ,
368
+ )
369
+ . build ( )
370
+ . map_err ( log_error) ?,
371
+ )
372
+ . build ( ) ,
366
373
)
367
374
. send ( )
368
375
. await
369
- . map_err ( log_error) ?
370
- else {
376
+ . map_err ( log_error) ?;
377
+
378
+ let item = output
379
+ . responses
380
+ . and_then ( |responses| responses. into_iter ( ) . next ( ) )
381
+ . and_then ( |response| response. item ) ;
382
+
383
+ let Some ( mut current_item) = item else {
371
384
return Ok ( None ) ;
372
385
} ;
373
386
@@ -384,38 +397,47 @@ impl Cas for CompareAndSwap {
384
397
}
385
398
}
386
399
387
- /// `swap` updates the value for the key using the etag saved in the `current` function for
400
+ /// `swap` updates the value for the key using the version saved in the `current` function for
388
401
/// optimistic concurrency.
389
402
async fn swap ( & self , value : Vec < u8 > ) -> Result < ( ) , SwapError > {
390
- let mut update_item = self
391
- . client
392
- . update_item ( )
403
+ let mut update_item = Update :: builder ( )
393
404
. table_name ( self . table . as_str ( ) )
394
405
. key ( PK , AttributeValue :: S ( self . key . clone ( ) ) )
395
406
. expression_attribute_names ( "#val" , VAL )
396
407
. expression_attribute_values ( ":val" , AttributeValue :: B ( Blob :: new ( value) ) )
397
408
. expression_attribute_names ( "#ver" , VER )
398
409
. expression_attribute_values ( ":increment" , AttributeValue :: N ( "1" . to_owned ( ) ) )
399
- . return_values ( aws_sdk_dynamodb:: types:: ReturnValue :: UpdatedNew ) ;
410
+ . return_values_on_condition_check_failure (
411
+ aws_sdk_dynamodb:: types:: ReturnValuesOnConditionCheckFailure :: None ,
412
+ ) ;
400
413
401
414
let current_version = self . version . lock ( ) . unwrap ( ) . clone ( ) ;
402
415
match current_version {
403
- // Existing item with version key , update under condition that version in DynamoDB matches stored version (optimistic lock)
416
+ // Existing item with version, update under condition that version in DynamoDB matches cached version
404
417
Some ( version) => {
405
418
update_item = update_item
406
419
. update_expression ( "SET #val=:val ADD #ver :increment" )
407
420
. condition_expression ( "#ver = :ver" )
408
421
. expression_attribute_values ( ":ver" , AttributeValue :: N ( version) ) ;
409
422
}
410
- // Assume new /unversioned item, upsert under condition that item does not already have a version -- if it does, another atomic operation has already started
423
+ // New /unversioned item, upsert atomically but without optimistic locking guarantee
411
424
None => {
412
- update_item = update_item
413
- . condition_expression ( "attribute_not_exists(#ver)" )
414
- . update_expression ( "SET #val=:val, #ver=:increment" ) ;
425
+ update_item = update_item. update_expression ( "SET #val=:val, #ver=:increment" ) ;
415
426
}
416
427
} ;
417
428
418
- update_item
429
+ // TransactWriteItems fails if concurrent writes are in progress on an item.
430
+ self . client
431
+ . transact_write_items ( )
432
+ . transact_items (
433
+ TransactWriteItem :: builder ( )
434
+ . update (
435
+ update_item
436
+ . build ( )
437
+ . map_err ( |e| SwapError :: Other ( format ! ( "{e:?}" ) ) ) ?,
438
+ )
439
+ . build ( ) ,
440
+ )
419
441
. send ( )
420
442
. await
421
443
. map_err ( |e| SwapError :: CasFailed ( format ! ( "{e:?}" ) ) ) ?;
0 commit comments