blob: 58b21f59cde298304fd02afafcdf6acfc50e9924 [file] [log] [blame]
Serge Bazanski5faa2fc2020-09-07 14:09:30 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package logtree
18
19// entry is a journal entry, representing a single log event (encompassed in a Payload) at a given DN.
20// See the journal struct for more information about the global/local linked lists.
21type entry struct {
22 // origin is the DN at which the log entry was recorded, or conversely, in which DN it will be available at.
23 origin DN
24 // journal is the parent journal of this entry. An entry can belong only to a single journal. This pointer is used
25 // to mutate the journal's head/tail pointers when unlinking an entry.
26 journal *journal
Serge Bazanski1bfa0c22020-10-14 16:45:07 +020027 // payload is the inner log entry LeveledPayload. It contains all data and metadata received from the log producer.
28 leveled *LeveledPayload
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020029
30 // prevGlobal is the previous entry in the global linked list, or nil if this entry is the oldest entry in the
31 // global linked list.
32 prevGlobal *entry
33 // nextGlobal is the next entry in the global linked list, or nil if this entry is the newest entry in the global
34 // linked list.
35 nextGlobal *entry
36
37 // prevLocal is the previous entry in this entry DN's local linked list, or nil if this entry is the oldest entry in
38 // this local linked list.
39 prevLocal *entry
40 // prevLocal is the next entry in this entry DN's local linked list, or nil if this entry is the newest entry in
41 // this local linked list.
42 nextLocal *entry
43
44 // seqLocal is a counter within a local linked list that increases by one each time a new log entry is added. It is
45 // used to quickly establish local linked list sizes (by subtracting seqLocal from both ends). This setup allows for
46 // O(1) length calculation for local linked lists as long as entries are only unlinked from the head or tail (which
47 // is the case in the current implementation).
48 seqLocal uint64
49}
50
51// unlink removes this entry from both global and local linked lists, updating the journal's head/tail pointers if
52// needed.
53// journal.mu must be taken as RW
54func (e *entry) unlink() {
55 // Unlink from the global linked list.
56 if e.prevGlobal != nil {
57 e.prevGlobal.nextGlobal = e.nextGlobal
58 }
59 if e.nextGlobal != nil {
60 e.nextGlobal.prevGlobal = e.prevGlobal
61 }
62 // Update journal head/tail pointers.
63 if e.journal.head == e {
64 e.journal.head = e.prevGlobal
65 }
66 if e.journal.tail == e {
67 e.journal.tail = e.nextGlobal
68 }
69
70 // Unlink from the local linked list.
71 if e.prevLocal != nil {
72 e.prevLocal.nextLocal = e.nextLocal
73 }
74 if e.nextLocal != nil {
75 e.nextLocal.prevLocal = e.prevLocal
76 }
77 // Update journal head/tail pointers.
78 if e.journal.heads[e.origin] == e {
79 e.journal.heads[e.origin] = e.prevLocal
80 }
81 if e.journal.tails[e.origin] == e {
82 e.journal.tails[e.origin] = e.nextLocal
83 }
84}
85
86// quota describes the quota policy for logging at a given DN.
87type quota struct {
88 // origin is the exact DN that this quota applies to.
89 origin DN
90 // max is the maximum count of log entries permitted for this DN - ie, the maximum size of the local linked list.
91 max uint64
92}
93
94// append adds an entry at the head of the global and local linked lists.
95func (j *journal) append(e *entry) {
96 j.mu.Lock()
97 defer j.mu.Unlock()
98
99 e.journal = j
100
101 // Insert at head in global linked list, set pointers.
102 e.nextGlobal = nil
103 e.prevGlobal = j.head
104 if j.head != nil {
105 j.head.nextGlobal = e
106 }
107 j.head = e
108 if j.tail == nil {
109 j.tail = e
110 }
111
112 // Create quota if necessary.
113 if _, ok := j.quota[e.origin]; !ok {
114 j.quota[e.origin] = &quota{origin: e.origin, max: 8192}
115 }
116
117 // Insert at head in local linked list, calculate seqLocal, set pointers.
118 e.nextLocal = nil
119 e.prevLocal = j.heads[e.origin]
120 if j.heads[e.origin] != nil {
121 j.heads[e.origin].nextLocal = e
122 e.seqLocal = e.prevLocal.seqLocal + 1
123 } else {
124 e.seqLocal = 0
125 }
126 j.heads[e.origin] = e
127 if j.tails[e.origin] == nil {
128 j.tails[e.origin] = e
129 }
130
131 // Apply quota to the local linked list that this entry got inserted to, ie. remove elements in excess of the
132 // quota.max count.
133 quota := j.quota[e.origin]
134 count := (j.heads[e.origin].seqLocal - j.tails[e.origin].seqLocal) + 1
135 if count > quota.max {
136 // Keep popping elements off the tail of the local linked list until quota is not violated.
137 left := count - quota.max
138 cur := j.tails[e.origin]
139 for {
140 // This shouldn't happen if quota.max >= 1.
141 if cur == nil {
142 break
143 }
144 if left == 0 {
145 break
146 }
147 el := cur
148 cur = el.nextLocal
149 // Unlinking the entry unlinks it from both the global and local linked lists.
150 el.unlink()
151 left -= 1
152 }
153 }
154}