Skip to content

Commit 77e53d3

Browse files
committed
Refactor: Implement Clock replacement algorithm for buffer management
1 parent 86c59ba commit 77e53d3

2 files changed

Lines changed: 170 additions & 0 deletions

File tree

buffer/Clock.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package buffer
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"sync"
8+
"ultraSQL/kfile"
9+
)
10+
11+
// Clock implements the Clock (Second Chance) replacement algorithm.
12+
// It maintains a circular buffer of frames with a reference bit for each frame.
13+
type Clock struct {
14+
fm *kfile.FileMgr
15+
capacity int
16+
bufferPool map[kfile.BlockId]*Buffer // Maps BlockId to Buffer
17+
frames []*Buffer // Circular buffer of frames
18+
clockHand int // Current position of clock hand
19+
mu sync.Mutex // Ensures thread safety
20+
}
21+
22+
// InitClock creates a new Clock replacement policy with the given capacity.
23+
func InitClock(capacity int, fm *kfile.FileMgr) *Clock {
24+
return &Clock{
25+
fm: fm,
26+
capacity: capacity,
27+
bufferPool: make(map[kfile.BlockId]*Buffer),
28+
frames: make([]*Buffer, capacity),
29+
clockHand: 0,
30+
}
31+
}
32+
33+
// AllocateBufferForBlock implements the buffer allocation strategy for the Clock algorithm.
34+
func (c *Clock) AllocateBufferForBlock(block kfile.BlockId) (*Buffer, error) {
35+
c.mu.Lock()
36+
defer c.mu.Unlock()
37+
38+
// Check if block already exists
39+
if buff, exists := c.bufferPool[block]; exists {
40+
buff.setReferenced(true) // Set reference bit
41+
buff.Pin()
42+
return buff, nil
43+
}
44+
45+
// Find an empty frame or evict one
46+
buff := NewBuffer(c.fm)
47+
var err error
48+
49+
// First, try to find an empty frame
50+
for i, frame := range c.frames {
51+
if frame == nil {
52+
buff = NewBuffer(c.fm)
53+
c.frames[i] = buff
54+
break
55+
}
56+
}
57+
58+
// If no empty frame, need to evict
59+
if buff == nil {
60+
buff, err = c.evictLocked()
61+
if err != nil {
62+
return nil, fmt.Errorf("failed to evict buffer: %w", err)
63+
}
64+
}
65+
66+
// Assign the new block to the buffer
67+
if err := buff.assignToBlock(&block); err != nil {
68+
if !errors.Is(err, io.EOF) {
69+
return nil, fmt.Errorf("failed to assign block to buffer: %w", err)
70+
}
71+
}
72+
73+
buff.setReferenced(true) // Set reference bit for new buffer
74+
buff.Pin()
75+
c.bufferPool[block] = buff
76+
77+
return buff, nil
78+
}
79+
80+
// Get retrieves a buffer containing the specified block.
81+
func (c *Clock) Get(block kfile.BlockId) (*Buffer, error) {
82+
c.mu.Lock()
83+
defer c.mu.Unlock()
84+
85+
if buff, exists := c.bufferPool[block]; exists {
86+
buff.setReferenced(true) // Set reference bit
87+
buff.Pin()
88+
return buff, nil
89+
}
90+
return nil, fmt.Errorf("buffer for block %v does not exist", block)
91+
}
92+
93+
// evictLocked implements the clock algorithm's eviction strategy.
94+
// The caller must hold c.mu.
95+
func (c *Clock) evictLocked() (*Buffer, error) {
96+
startingHand := c.clockHand
97+
98+
// Make up to two passes:
99+
// First pass: Look for unreferenced page
100+
// Second pass: Clear references and try again
101+
for pass := 0; pass < 2; pass++ {
102+
for {
103+
buff := c.frames[c.clockHand]
104+
105+
// Advance clock hand
106+
c.clockHand = (c.clockHand + 1) % c.capacity
107+
108+
// Skip if buffer is nil or pinned
109+
if buff == nil || buff.Pinned() {
110+
if c.clockHand == startingHand {
111+
break // Completed full circle
112+
}
113+
continue
114+
}
115+
116+
if pass == 0 {
117+
// First pass: if referenced, clear bit and continue
118+
if buff.referenced() {
119+
buff.setReferenced(false)
120+
if c.clockHand == startingHand {
121+
break // Completed full circle
122+
}
123+
continue
124+
}
125+
}
126+
127+
// Found a victim: unreferenced and unpinned
128+
if block := buff.Block(); block != nil {
129+
delete(c.bufferPool, *block)
130+
}
131+
return buff, nil
132+
}
133+
}
134+
135+
return nil, ErrNoUnpinnedBuffers
136+
}
137+
138+
// Evict implements the EvictionPolicy interface.
139+
func (c *Clock) Evict() (*Buffer, error) {
140+
c.mu.Lock()
141+
defer c.mu.Unlock()
142+
return c.evictLocked()
143+
}
144+
145+
// FlushAll implements the EvictionPolicy interface.
146+
func (c *Clock) FlushAll(txnum int) {
147+
c.mu.Lock()
148+
defer c.mu.Unlock()
149+
150+
for _, buff := range c.frames {
151+
if buff != nil && buff.ModifyingTxID() == txnum {
152+
_ = buff.Flush()
153+
}
154+
}
155+
}

buffer/buffer.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"io"
9+
"sync"
910
"ultraSQL/kfile"
1011
)
1112

@@ -21,6 +22,8 @@ type Buffer struct {
2122
Dirty bool
2223
lastAccessTime uint64
2324
prev, next *Buffer
25+
refBit bool
26+
mu sync.Mutex
2427
}
2528

2629
// NewBuffer ...
@@ -150,3 +153,15 @@ func (b *Buffer) decompressPage(page *kfile.Page) error {
150153
func (b *Buffer) ModifyingTxID() int {
151154
return b.txnum
152155
}
156+
157+
func (b *Buffer) referenced() bool {
158+
b.mu.Lock()
159+
defer b.mu.Unlock()
160+
return b.refBit
161+
}
162+
163+
func (b *Buffer) setReferenced(ref bool) {
164+
b.mu.Lock()
165+
defer b.mu.Unlock()
166+
b.refBit = ref
167+
}

0 commit comments

Comments
 (0)