@@ -709,7 +709,7 @@ pub mod context {
709
709
timestamp : Cell < ReseedingTimestamp > ,
710
710
counter : Cell < Counter > ,
711
711
adjust : Adjust ,
712
- additional_precision_bits : usize ,
712
+ precision : Precision ,
713
713
}
714
714
715
715
impl RefUnwindSafe for ContextV7 { }
@@ -726,7 +726,12 @@ pub mod context {
726
726
} ) ,
727
727
counter : Cell :: new ( Counter { value : 0 } ) ,
728
728
adjust : Adjust { by_ns : 0 } ,
729
- additional_precision_bits : 0 ,
729
+ precision : Precision {
730
+ bits : 0 ,
731
+ mask : 0 ,
732
+ factor : 0 ,
733
+ shift : 0 ,
734
+ } ,
730
735
}
731
736
}
732
737
@@ -742,8 +747,8 @@ pub mod context {
742
747
/// by trading a small amount of entropy for better counter synchronization. Note that the counter
743
748
/// will still be reseeded on millisecond boundaries, even though some of its storage will be
744
749
/// dedicated to the timestamp.
745
- pub fn with_nanosecond_precision ( mut self ) -> Self {
746
- self . additional_precision_bits = 12 ;
750
+ pub fn with_additional_precision ( mut self ) -> Self {
751
+ self . precision = Precision :: new ( 12 ) ;
747
752
self
748
753
}
749
754
}
@@ -768,26 +773,23 @@ pub mod context {
768
773
769
774
if should_reseed {
770
775
// If the observed system time has shifted forwards then regenerate the counter
771
- counter = Counter :: reseed ( self . additional_precision_bits , & timestamp) ;
776
+ counter = Counter :: reseed ( & self . precision , & timestamp) ;
772
777
} else {
773
778
// If the observed system time has not shifted forwards then increment the counter
774
779
775
780
// If the incoming timestamp is earlier than the last observed one then
776
781
// use it instead. This may happen if the system clock jitters, or if the counter
777
782
// has wrapped and the timestamp is artificially incremented
778
783
779
- counter = self
780
- . counter
781
- . get ( )
782
- . increment ( self . additional_precision_bits , & timestamp) ;
784
+ counter = self . counter . get ( ) . increment ( & self . precision , & timestamp) ;
783
785
784
786
// Unlikely: If the counter has overflowed its 42-bit storage then wrap it
785
787
// and increment the timestamp. Until the observed system time shifts past
786
788
// this incremented value, all timestamps will use it to maintain monotonicity
787
789
if counter. has_overflowed ( ) {
788
790
// Increment the timestamp by 1 milli and reseed the counter
789
791
timestamp = timestamp. increment ( ) ;
790
- counter = Counter :: reseed ( self . additional_precision_bits , & timestamp) ;
792
+ counter = Counter :: reseed ( & self . precision , & timestamp) ;
791
793
}
792
794
} ;
793
795
@@ -802,6 +804,46 @@ pub mod context {
802
804
}
803
805
}
804
806
807
+ #[ derive( Debug ) ]
808
+ struct Adjust {
809
+ by_ns : u32 ,
810
+ }
811
+
812
+ impl Adjust {
813
+ #[ inline]
814
+ fn by_millis ( millis : u32 ) -> Self {
815
+ Adjust {
816
+ by_ns : millis. saturating_mul ( 1_000_000 ) ,
817
+ }
818
+ }
819
+
820
+ #[ inline]
821
+ fn apply ( & self , seconds : u64 , subsec_nanos : u32 ) -> ( u64 , u32 ) {
822
+ if self . by_ns == 0 {
823
+ // No shift applied
824
+ return ( seconds, subsec_nanos) ;
825
+ }
826
+
827
+ let mut shifted_subsec_nanos =
828
+ subsec_nanos. checked_add ( self . by_ns ) . unwrap_or ( subsec_nanos) ;
829
+
830
+ if shifted_subsec_nanos < 1_000_000_000 {
831
+ // The shift hasn't overflowed into the next second
832
+ ( seconds, shifted_subsec_nanos)
833
+ } else {
834
+ // The shift has overflowed into the next second
835
+ shifted_subsec_nanos -= 1_000_000_000 ;
836
+
837
+ if seconds < u64:: MAX {
838
+ ( seconds + 1 , shifted_subsec_nanos)
839
+ } else {
840
+ // The next second would overflow a `u64`
841
+ ( seconds, subsec_nanos)
842
+ }
843
+ }
844
+ }
845
+ }
846
+
805
847
#[ derive( Debug , Default , Clone , Copy ) ]
806
848
struct ReseedingTimestamp {
807
849
last_seed : u64 ,
@@ -854,42 +896,42 @@ pub mod context {
854
896
}
855
897
856
898
#[ derive( Debug ) ]
857
- struct Adjust {
858
- by_ns : u32 ,
899
+ struct Precision {
900
+ bits : usize ,
901
+ factor : u64 ,
902
+ mask : u64 ,
903
+ shift : u64 ,
859
904
}
860
905
861
- impl Adjust {
862
- #[ inline]
863
- fn by_millis ( millis : u32 ) -> Self {
864
- Adjust {
865
- by_ns : millis. saturating_mul ( 1_000_000 ) ,
906
+ impl Precision {
907
+ fn new ( bits : usize ) -> Self {
908
+ // The mask and shift are used to paste the sub-millisecond precision
909
+ // into the most significant bits of the counter
910
+ let mask = u64:: MAX >> ( 64 - USABLE_BITS + bits) ;
911
+ let shift = ( USABLE_BITS - bits) as u64 ;
912
+
913
+ // The factor reduces the size of the sub-millisecond precision to
914
+ // fit into the specified number of bits
915
+ let factor = ( 999_999u64 / 2u64 . pow ( bits as u32 ) ) + 1 ;
916
+
917
+ Precision {
918
+ bits,
919
+ factor,
920
+ mask,
921
+ shift,
866
922
}
867
923
}
868
924
869
925
#[ inline]
870
- fn apply ( & self , seconds : u64 , subsec_nanos : u32 ) -> ( u64 , u32 ) {
871
- if self . by_ns == 0 {
872
- // No shift applied
873
- return ( seconds , subsec_nanos ) ;
926
+ fn apply ( & self , value : u64 , timestamp : & ReseedingTimestamp ) -> u64 {
927
+ if self . bits == 0 {
928
+ // No additional precision is being used
929
+ return value ;
874
930
}
875
931
876
- let mut shifted_subsec_nanos =
877
- subsec_nanos. checked_add ( self . by_ns ) . unwrap_or ( subsec_nanos) ;
878
-
879
- if shifted_subsec_nanos < 1_000_000_000 {
880
- // The shift hasn't overflowed into the next second
881
- ( seconds, shifted_subsec_nanos)
882
- } else {
883
- // The shift has overflowed into the next second
884
- shifted_subsec_nanos -= 1_000_000_000 ;
932
+ let additional = timestamp. submilli_nanos ( ) as u64 / self . factor ;
885
933
886
- if seconds < u64:: MAX {
887
- ( seconds + 1 , shifted_subsec_nanos)
888
- } else {
889
- // The next second would overflow a `u64`
890
- ( seconds, subsec_nanos)
891
- }
892
- }
934
+ ( value & self . mask ) | ( additional << self . shift )
893
935
}
894
936
}
895
937
@@ -900,42 +942,22 @@ pub mod context {
900
942
901
943
impl Counter {
902
944
#[ inline]
903
- fn new (
904
- value : u64 ,
905
- additional_precision_bits : usize ,
906
- timestamp : & ReseedingTimestamp ,
907
- ) -> Self {
945
+ fn reseed ( precision : & Precision , timestamp : & ReseedingTimestamp ) -> Self {
908
946
Counter {
909
- value : if additional_precision_bits != 0 {
910
- let precision_mask =
911
- u64:: MAX >> ( 64 - USABLE_BITS + additional_precision_bits) ;
912
- let precision_shift = USABLE_BITS - additional_precision_bits;
913
-
914
- let precision = timestamp. submilli_nanos ( ) as u64 ;
915
-
916
- ( value & precision_mask) | ( precision << precision_shift)
917
- } else {
918
- value
919
- } ,
947
+ value : precision. apply ( crate :: rng:: u64 ( ) & RESEED_MASK , timestamp) ,
920
948
}
921
949
}
922
950
923
951
#[ inline]
924
- fn reseed ( additional_precision_bits : usize , timestamp : & ReseedingTimestamp ) -> Self {
925
- Counter :: new (
926
- crate :: rng:: u64 ( ) & RESEED_MASK ,
927
- additional_precision_bits,
928
- timestamp,
929
- )
930
- }
952
+ fn increment ( & self , precision : & Precision , timestamp : & ReseedingTimestamp ) -> Self {
953
+ let mut counter = Counter {
954
+ value : precision. apply ( self . value , timestamp) ,
955
+ } ;
931
956
932
- #[ inline]
933
- fn increment (
934
- & self ,
935
- additional_precision_bits : usize ,
936
- timestamp : & ReseedingTimestamp ,
937
- ) -> Self {
938
- let mut counter = Counter :: new ( self . value , additional_precision_bits, timestamp) ;
957
+ // We unconditionally increment the counter even though the precision
958
+ // may have set higher bits already. This could technically be avoided,
959
+ // but the higher bits are a coarse approximation so we just avoid the
960
+ // `if` branch and increment it either way
939
961
940
962
// Guaranteed to never overflow u64
941
963
counter. value += 1 ;
@@ -982,7 +1004,7 @@ pub mod context {
982
1004
983
1005
use super :: * ;
984
1006
985
- use crate :: Timestamp ;
1007
+ use crate :: { Timestamp , Uuid } ;
986
1008
987
1009
#[ test]
988
1010
fn context ( ) {
@@ -1024,7 +1046,12 @@ pub mod context {
1024
1046
let context = ContextV7 {
1025
1047
timestamp : Cell :: new ( ReseedingTimestamp :: from_ts ( seconds, subsec_nanos) ) ,
1026
1048
adjust : Adjust :: by_millis ( 0 ) ,
1027
- additional_precision_bits : 0 ,
1049
+ precision : Precision {
1050
+ bits : 0 ,
1051
+ mask : 0 ,
1052
+ factor : 0 ,
1053
+ shift : 0 ,
1054
+ } ,
1028
1055
counter : Cell :: new ( Counter {
1029
1056
value : u64:: MAX >> 22 ,
1030
1057
} ) ,
@@ -1059,15 +1086,26 @@ pub mod context {
1059
1086
let seconds = 1_496_854_535 ;
1060
1087
let subsec_nanos = 812_946_000 ;
1061
1088
1062
- let context = ContextV7 :: new ( ) . with_nanosecond_precision ( ) ;
1089
+ let context = ContextV7 :: new ( ) . with_additional_precision ( ) ;
1063
1090
1064
- let ts = Timestamp :: from_unix ( & context, seconds, subsec_nanos) ;
1091
+ let ts1 = Timestamp :: from_unix ( & context, seconds, subsec_nanos) ;
1092
+
1093
+ // NOTE: Future changes in rounding may change this value slightly
1094
+ assert_eq ! ( 3861 , ts1. counter >> 30 ) ;
1065
1095
1066
- let ( counter, width ) = ts . counter ( ) ;
1096
+ assert ! ( ts1 . counter < ( u64 :: MAX >> 22 ) as u128 ) ;
1067
1097
1068
- assert_eq ! ( 946_000 , counter >> 30 ) ;
1098
+ // Generate another timestamp; it should continue to sort
1099
+ let ts2 = Timestamp :: from_unix ( & context, seconds, subsec_nanos) ;
1100
+
1101
+ assert ! ( Uuid :: new_v7( ts2) > Uuid :: new_v7( ts1) ) ;
1102
+
1103
+ // Generate another timestamp with an extra nanosecond
1104
+ let subsec_nanos = subsec_nanos + 1 ;
1105
+
1106
+ let ts3 = Timestamp :: from_unix ( & context, seconds, subsec_nanos) ;
1069
1107
1070
- assert_eq ! ( 42 , width ) ;
1108
+ assert ! ( Uuid :: new_v7 ( ts3 ) > Uuid :: new_v7 ( ts2 ) ) ;
1071
1109
}
1072
1110
}
1073
1111
}
0 commit comments