@@ -180,12 +180,12 @@ impl LoadedProgram {
180
180
}
181
181
}
182
182
183
- pub fn new_tombstone ( ) -> Self {
183
+ pub fn new_tombstone ( slot : Slot ) -> Self {
184
184
Self {
185
185
program : LoadedProgramType :: Invalid ,
186
186
account_size : 0 ,
187
- deployment_slot : 0 ,
188
- effective_slot : 0 ,
187
+ deployment_slot : slot ,
188
+ effective_slot : slot ,
189
189
usage_counter : AtomicU64 :: default ( ) ,
190
190
}
191
191
}
@@ -219,8 +219,10 @@ pub enum LoadedProgramEntry {
219
219
}
220
220
221
221
impl LoadedPrograms {
222
- /// Inserts a single entry
223
- pub fn insert_entry ( & mut self , key : Pubkey , entry : LoadedProgram ) -> LoadedProgramEntry {
222
+ /// Refill the cache with a single entry. It's typically called during transaction processing,
223
+ /// when the cache doesn't contain the entry corresponding to program `key`.
224
+ /// The function dedupes the cache, in case some other thread replenished the the entry in parallel.
225
+ pub fn replenish ( & mut self , key : Pubkey , entry : Arc < LoadedProgram > ) -> LoadedProgramEntry {
224
226
let second_level = self . entries . entry ( key) . or_insert_with ( Vec :: new) ;
225
227
let index = second_level
226
228
. iter ( )
@@ -235,9 +237,32 @@ impl LoadedPrograms {
235
237
return LoadedProgramEntry :: WasOccupied ( existing. clone ( ) ) ;
236
238
}
237
239
}
238
- let new_entry = Arc :: new ( entry) ;
239
- second_level. insert ( index. unwrap_or ( second_level. len ( ) ) , new_entry. clone ( ) ) ;
240
- LoadedProgramEntry :: WasVacant ( new_entry)
240
+ second_level. insert ( index. unwrap_or ( second_level. len ( ) ) , entry. clone ( ) ) ;
241
+ LoadedProgramEntry :: WasVacant ( entry)
242
+ }
243
+
244
+ /// Assign the program `entry` to the given `key` in the cache.
245
+ /// This is typically called when a deployed program is managed (upgraded/un/reddeployed) via
246
+ /// bpf loader instructions.
247
+ /// The program management is not expected to overlap with initial program deployment slot.
248
+ /// Note: Do not call this function to replenish cache with a missing entry. As that use-case can
249
+ /// cause the cache to have duplicates. Use `replenish()` API for that use-case.
250
+ pub fn assign_program ( & mut self , key : Pubkey , entry : Arc < LoadedProgram > ) -> Arc < LoadedProgram > {
251
+ let second_level = self . entries . entry ( key) . or_insert_with ( Vec :: new) ;
252
+ let index = second_level
253
+ . iter ( )
254
+ . position ( |at| at. effective_slot >= entry. effective_slot ) ;
255
+ if let Some ( index) = index {
256
+ let existing = second_level
257
+ . get ( index)
258
+ . expect ( "Missing entry, even though position was found" ) ;
259
+ assert ! (
260
+ existing. deployment_slot != entry. deployment_slot
261
+ || existing. effective_slot != entry. effective_slot
262
+ ) ;
263
+ }
264
+ second_level. insert ( index. unwrap_or ( second_level. len ( ) ) , entry. clone ( ) ) ;
265
+ entry
241
266
}
242
267
243
268
/// Before rerooting the blockstore this removes all programs of orphan forks
@@ -310,6 +335,7 @@ mod tests {
310
335
BlockRelation , ForkGraph , LoadedProgram , LoadedProgramEntry , LoadedProgramType ,
311
336
LoadedPrograms , WorkingSlot ,
312
337
} ,
338
+ solana_rbpf:: vm:: BuiltInProgram ,
313
339
solana_sdk:: { clock:: Slot , pubkey:: Pubkey } ,
314
340
std:: {
315
341
collections:: HashMap ,
@@ -318,11 +344,70 @@ mod tests {
318
344
} ,
319
345
} ;
320
346
347
+ fn new_test_builtin_program ( deployment_slot : Slot , effective_slot : Slot ) -> Arc < LoadedProgram > {
348
+ Arc :: new ( LoadedProgram {
349
+ program : LoadedProgramType :: BuiltIn ( BuiltInProgram :: default ( ) ) ,
350
+ account_size : 0 ,
351
+ deployment_slot,
352
+ effective_slot,
353
+ usage_counter : AtomicU64 :: default ( ) ,
354
+ } )
355
+ }
356
+
357
+ fn set_tombstone ( cache : & mut LoadedPrograms , key : Pubkey , slot : Slot ) -> Arc < LoadedProgram > {
358
+ cache. assign_program ( key, Arc :: new ( LoadedProgram :: new_tombstone ( slot) ) )
359
+ }
360
+
321
361
#[ test]
322
362
fn test_tombstone ( ) {
323
- let tombstone = LoadedProgram :: new_tombstone ( ) ;
363
+ let tombstone = LoadedProgram :: new_tombstone ( 0 ) ;
364
+ assert ! ( matches!( tombstone. program, LoadedProgramType :: Invalid ) ) ;
365
+ assert ! ( tombstone. is_tombstone( ) ) ;
366
+ assert_eq ! ( tombstone. deployment_slot, 0 ) ;
367
+ assert_eq ! ( tombstone. effective_slot, 0 ) ;
368
+
369
+ let tombstone = LoadedProgram :: new_tombstone ( 100 ) ;
324
370
assert ! ( matches!( tombstone. program, LoadedProgramType :: Invalid ) ) ;
325
371
assert ! ( tombstone. is_tombstone( ) ) ;
372
+ assert_eq ! ( tombstone. deployment_slot, 100 ) ;
373
+ assert_eq ! ( tombstone. effective_slot, 100 ) ;
374
+
375
+ let mut cache = LoadedPrograms :: default ( ) ;
376
+ let program1 = Pubkey :: new_unique ( ) ;
377
+ let tombstone = set_tombstone ( & mut cache, program1, 10 ) ;
378
+ let second_level = & cache
379
+ . entries
380
+ . get ( & program1)
381
+ . expect ( "Failed to find the entry" ) ;
382
+ assert_eq ! ( second_level. len( ) , 1 ) ;
383
+ assert ! ( second_level. get( 0 ) . unwrap( ) . is_tombstone( ) ) ;
384
+ assert_eq ! ( tombstone. deployment_slot, 10 ) ;
385
+ assert_eq ! ( tombstone. effective_slot, 10 ) ;
386
+
387
+ // Add a program at slot 50, and a tombstone for the program at slot 60
388
+ let program2 = Pubkey :: new_unique ( ) ;
389
+ assert ! ( matches!(
390
+ cache. replenish( program2, new_test_builtin_program( 50 , 51 ) ) ,
391
+ LoadedProgramEntry :: WasVacant ( _)
392
+ ) ) ;
393
+ let second_level = & cache
394
+ . entries
395
+ . get ( & program2)
396
+ . expect ( "Failed to find the entry" ) ;
397
+ assert_eq ! ( second_level. len( ) , 1 ) ;
398
+ assert ! ( !second_level. get( 0 ) . unwrap( ) . is_tombstone( ) ) ;
399
+
400
+ let tombstone = set_tombstone ( & mut cache, program2, 60 ) ;
401
+ let second_level = & cache
402
+ . entries
403
+ . get ( & program2)
404
+ . expect ( "Failed to find the entry" ) ;
405
+ assert_eq ! ( second_level. len( ) , 2 ) ;
406
+ assert ! ( !second_level. get( 0 ) . unwrap( ) . is_tombstone( ) ) ;
407
+ assert ! ( second_level. get( 1 ) . unwrap( ) . is_tombstone( ) ) ;
408
+ assert ! ( tombstone. is_tombstone( ) ) ;
409
+ assert_eq ! ( tombstone. deployment_slot, 60 ) ;
410
+ assert_eq ! ( tombstone. effective_slot, 60 ) ;
326
411
}
327
412
328
413
struct TestForkGraph {
@@ -464,14 +549,14 @@ mod tests {
464
549
}
465
550
}
466
551
467
- fn new_test_loaded_program ( deployment_slot : Slot , effective_slot : Slot ) -> LoadedProgram {
468
- LoadedProgram {
552
+ fn new_test_loaded_program ( deployment_slot : Slot , effective_slot : Slot ) -> Arc < LoadedProgram > {
553
+ Arc :: new ( LoadedProgram {
469
554
program : LoadedProgramType :: Invalid ,
470
555
account_size : 0 ,
471
556
deployment_slot,
472
557
effective_slot,
473
558
usage_counter : AtomicU64 :: default ( ) ,
474
- }
559
+ } )
475
560
}
476
561
477
562
fn match_slot (
@@ -511,52 +596,52 @@ mod tests {
511
596
512
597
let program1 = Pubkey :: new_unique ( ) ;
513
598
assert ! ( matches!(
514
- cache. insert_entry ( program1, new_test_loaded_program( 0 , 1 ) ) ,
599
+ cache. replenish ( program1, new_test_loaded_program( 0 , 1 ) ) ,
515
600
LoadedProgramEntry :: WasVacant ( _)
516
601
) ) ;
517
602
assert ! ( matches!(
518
- cache. insert_entry ( program1, new_test_loaded_program( 10 , 11 ) ) ,
603
+ cache. replenish ( program1, new_test_loaded_program( 10 , 11 ) ) ,
519
604
LoadedProgramEntry :: WasVacant ( _)
520
605
) ) ;
521
606
assert ! ( matches!(
522
- cache. insert_entry ( program1, new_test_loaded_program( 20 , 21 ) ) ,
607
+ cache. replenish ( program1, new_test_loaded_program( 20 , 21 ) ) ,
523
608
LoadedProgramEntry :: WasVacant ( _)
524
609
) ) ;
525
610
526
611
// Test: inserting duplicate entry return pre existing entry from the cache
527
612
assert ! ( matches!(
528
- cache. insert_entry ( program1, new_test_loaded_program( 20 , 21 ) ) ,
613
+ cache. replenish ( program1, new_test_loaded_program( 20 , 21 ) ) ,
529
614
LoadedProgramEntry :: WasOccupied ( _)
530
615
) ) ;
531
616
532
617
let program2 = Pubkey :: new_unique ( ) ;
533
618
assert ! ( matches!(
534
- cache. insert_entry ( program2, new_test_loaded_program( 5 , 6 ) ) ,
619
+ cache. replenish ( program2, new_test_loaded_program( 5 , 6 ) ) ,
535
620
LoadedProgramEntry :: WasVacant ( _)
536
621
) ) ;
537
622
assert ! ( matches!(
538
- cache. insert_entry ( program2, new_test_loaded_program( 11 , 12 ) ) ,
623
+ cache. replenish ( program2, new_test_loaded_program( 11 , 12 ) ) ,
539
624
LoadedProgramEntry :: WasVacant ( _)
540
625
) ) ;
541
626
542
627
let program3 = Pubkey :: new_unique ( ) ;
543
628
assert ! ( matches!(
544
- cache. insert_entry ( program3, new_test_loaded_program( 25 , 26 ) ) ,
629
+ cache. replenish ( program3, new_test_loaded_program( 25 , 26 ) ) ,
545
630
LoadedProgramEntry :: WasVacant ( _)
546
631
) ) ;
547
632
548
633
let program4 = Pubkey :: new_unique ( ) ;
549
634
assert ! ( matches!(
550
- cache. insert_entry ( program4, new_test_loaded_program( 0 , 1 ) ) ,
635
+ cache. replenish ( program4, new_test_loaded_program( 0 , 1 ) ) ,
551
636
LoadedProgramEntry :: WasVacant ( _)
552
637
) ) ;
553
638
assert ! ( matches!(
554
- cache. insert_entry ( program4, new_test_loaded_program( 5 , 6 ) ) ,
639
+ cache. replenish ( program4, new_test_loaded_program( 5 , 6 ) ) ,
555
640
LoadedProgramEntry :: WasVacant ( _)
556
641
) ) ;
557
642
// The following is a special case, where effective slot is 4 slots in the future
558
643
assert ! ( matches!(
559
- cache. insert_entry ( program4, new_test_loaded_program( 15 , 19 ) ) ,
644
+ cache. replenish ( program4, new_test_loaded_program( 15 , 19 ) ) ,
560
645
LoadedProgramEntry :: WasVacant ( _)
561
646
) ) ;
562
647
0 commit comments