@@ -709,7 +709,7 @@ pub mod context {
709709 timestamp : Cell < ReseedingTimestamp > ,
710710 counter : Cell < Counter > ,
711711 adjust : Adjust ,
712- additional_precision_bits : usize ,
712+ precision : Precision ,
713713 }
714714
715715 impl RefUnwindSafe for ContextV7 { }
@@ -726,7 +726,12 @@ pub mod context {
726726 } ) ,
727727 counter : Cell :: new ( Counter { value : 0 } ) ,
728728 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+ } ,
730735 }
731736 }
732737
@@ -742,8 +747,8 @@ pub mod context {
742747 /// by trading a small amount of entropy for better counter synchronization. Note that the counter
743748 /// will still be reseeded on millisecond boundaries, even though some of its storage will be
744749 /// 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 ) ;
747752 self
748753 }
749754 }
@@ -768,26 +773,23 @@ pub mod context {
768773
769774 if should_reseed {
770775 // 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) ;
772777 } else {
773778 // If the observed system time has not shifted forwards then increment the counter
774779
775780 // If the incoming timestamp is earlier than the last observed one then
776781 // use it instead. This may happen if the system clock jitters, or if the counter
777782 // has wrapped and the timestamp is artificially incremented
778783
779- counter = self
780- . counter
781- . get ( )
782- . increment ( self . additional_precision_bits , & timestamp) ;
784+ counter = self . counter . get ( ) . increment ( & self . precision , & timestamp) ;
783785
784786 // Unlikely: If the counter has overflowed its 42-bit storage then wrap it
785787 // and increment the timestamp. Until the observed system time shifts past
786788 // this incremented value, all timestamps will use it to maintain monotonicity
787789 if counter. has_overflowed ( ) {
788790 // Increment the timestamp by 1 milli and reseed the counter
789791 timestamp = timestamp. increment ( ) ;
790- counter = Counter :: reseed ( self . additional_precision_bits , & timestamp) ;
792+ counter = Counter :: reseed ( & self . precision , & timestamp) ;
791793 }
792794 } ;
793795
@@ -802,6 +804,46 @@ pub mod context {
802804 }
803805 }
804806
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+
805847 #[ derive( Debug , Default , Clone , Copy ) ]
806848 struct ReseedingTimestamp {
807849 last_seed : u64 ,
@@ -854,42 +896,42 @@ pub mod context {
854896 }
855897
856898 #[ derive( Debug ) ]
857- struct Adjust {
858- by_ns : u32 ,
899+ struct Precision {
900+ bits : usize ,
901+ factor : u64 ,
902+ mask : u64 ,
903+ shift : u64 ,
859904 }
860905
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,
866922 }
867923 }
868924
869925 #[ 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 ;
874930 }
875931
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 ;
885933
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 )
893935 }
894936 }
895937
@@ -900,42 +942,22 @@ pub mod context {
900942
901943 impl Counter {
902944 #[ 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 {
908946 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) ,
920948 }
921949 }
922950
923951 #[ 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+ } ;
931956
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
939961
940962 // Guaranteed to never overflow u64
941963 counter. value += 1 ;
@@ -982,7 +1004,7 @@ pub mod context {
9821004
9831005 use super :: * ;
9841006
985- use crate :: Timestamp ;
1007+ use crate :: { Timestamp , Uuid } ;
9861008
9871009 #[ test]
9881010 fn context ( ) {
@@ -1024,7 +1046,12 @@ pub mod context {
10241046 let context = ContextV7 {
10251047 timestamp : Cell :: new ( ReseedingTimestamp :: from_ts ( seconds, subsec_nanos) ) ,
10261048 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+ } ,
10281055 counter : Cell :: new ( Counter {
10291056 value : u64:: MAX >> 22 ,
10301057 } ) ,
@@ -1059,15 +1086,26 @@ pub mod context {
10591086 let seconds = 1_496_854_535 ;
10601087 let subsec_nanos = 812_946_000 ;
10611088
1062- let context = ContextV7 :: new ( ) . with_nanosecond_precision ( ) ;
1089+ let context = ContextV7 :: new ( ) . with_additional_precision ( ) ;
10631090
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 ) ;
10651095
1066- let ( counter, width ) = ts . counter ( ) ;
1096+ assert ! ( ts1 . counter < ( u64 :: MAX >> 22 ) as u128 ) ;
10671097
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) ;
10691107
1070- assert_eq ! ( 42 , width ) ;
1108+ assert ! ( Uuid :: new_v7 ( ts3 ) > Uuid :: new_v7 ( ts2 ) ) ;
10711109 }
10721110 }
10731111 }
0 commit comments