A skeleton web application configured to use Sinatra and ActiveRecord
at master 124 lines 3.1 kB view raw
1# 2# Copyright (c) 2019 joshua stein <jcs@jcs.org> 3# 4# Permission to use, copy, modify, and distribute this software for any 5# purpose with or without fee is hereby granted, provided that the above 6# copyright notice and this permission notice appear in all copies. 7# 8# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15# 16 17class UniqueId 18 # 19 # (roughly) time-sortable, incrementing ids that don't require an 20 # auto_increment database table and will not collide between processes and 21 # servers 22 # 23 # uses 64 bits, the size of a bigint in mysql 24 # 25 # [0, 32] = (time.to_f - EPOCH) * 10 26 # [32, 4] = node id, 0 to 15 27 # [36, 16] = pid 28 # [52, 12] = sequence, 0 to 4095 29 # 30 # this gives us 4096 ids per second, per pid, per node/server 31 # sequences are shared among threads in a process 32 # 33 34 # jan 1, 2019 00:00:00 +0000 35 EPOCH = 1546300800 36 37 LENGTHS = { 38 :time => 32, 39 :node => 4, 40 :pid => 16, 41 :sequence => 12, 42 }.freeze 43 44 attr_reader :binary, :time, :node, :pid, :sequence 45 46 def self.build_binary(time, node, pid, seq) 47 r = sprintf("%0#{LENGTHS[:time]}b%0#{LENGTHS[:node]}b" << 48 "%0#{LENGTHS[:pid]}b%0#{LENGTHS[:sequence]}b", time, node, pid, seq) 49 50 if r.length != 64 51 raise "created invalid length binary #{r.inspect} (#{r.length})" 52 end 53 54 r 55 end 56 57 def self.get 58 self.get_binary.to_i(2) 59 end 60 61 def self.get_binary 62 self.build_binary((Time.now.to_i - EPOCH), self.node, 63 self.truncate_pid($$), self.sequence) 64 end 65 66 def self.node 67 class_variable_defined?("@@node") ? @@node : 0 68 end 69 def self.node=(what) 70 @@node = what.to_i 71 end 72 73 def self.sequence 74 ret = 0 75 76 (@@sequence_mutex ||= Mutex.new).synchronize do 77 @@sequence ||= 0 78 79 if @@sequence >= (2 ** LENGTHS[:sequence]) - 1 80 ret = @@sequence = 0 81 else 82 ret = (@@sequence += 1) 83 end 84 end 85 86 ret 87 end 88 89 def self.parse(i) 90 UniqueId.new(i) 91 end 92 93 def self.truncate_pid(pid) 94 # pid is technically 17 bits on openbsd, capped at 99999, so cap at 16 bits 95 pid = pid.to_s(2) 96 if pid.length > LENGTHS[:pid] 97 pid = pid[pid.length - LENGTHS[:pid], LENGTHS[:pid]] 98 end 99 pid.to_i(2) 100 end 101 102 def initialize(i) 103 if !i || i > (2 ** 64) - 1 || i < 0 104 raise "invalid id #{i}" 105 end 106 107 @binary = sprintf("%064b", i) 108 109 @time = Time.at(EPOCH + @binary[0, LENGTHS[:time]].to_i(2)) 110 z = LENGTHS[:time] 111 112 @node = @binary[z, LENGTHS[:node]].to_i(2) 113 z += LENGTHS[:node] 114 115 @pid = @binary[z, LENGTHS[:pid]].to_i(2) 116 z += LENGTHS[:pid] 117 118 @sequence = @binary[z, LENGTHS[:sequence]].to_i(2) 119 end 120 121 def to_i 122 @binary.to_i(2) 123 end 124end