@@ -3,6 +3,7 @@ package bigqueue
33import (
44 "errors"
55 "fmt"
6+ "os"
67 "path"
78)
89
@@ -11,27 +12,33 @@ const (
1112)
1213
1314var (
14- // errShouldNotReach is returned when code reaches unexpected places
15+ // errShouldNotReach is returned when an invariant is not true anymore
1516 errShouldNotReach = errors .New ("SHOULD NOT REACH HERE" )
1617)
1718
1819// arenaManager manages all the arenas for a bigqueue
1920type arenaManager struct {
20- dir string
21- conf * bqConfig
22- baseAid int
23- arenaList []* arena
21+ dir string
22+ conf * bqConfig
23+ index * queueIndex
24+ baseAid int
25+ arenaList []* arena
26+ inMemArenas int
2427}
2528
2629// newArenaManager returns a pointer to new arenaManager
27- func newArenaManager (dir string , conf * bqConfig , headAid , tailAid int ) (
30+ func newArenaManager (dir string , conf * bqConfig , index * queueIndex ) (
2831 * arenaManager , error ) {
2932
33+ headAid , _ := index .getHead ()
34+ tailAid , _ := index .getTail ()
35+
3036 numArenas := tailAid + 1 - headAid
3137 arenaList := make ([]* arena , numArenas )
3238 am := & arenaManager {
3339 dir : dir ,
3440 conf : conf ,
41+ index : index ,
3542 baseAid : headAid ,
3643 arenaList : arenaList ,
3744 }
@@ -40,6 +47,7 @@ func newArenaManager(dir string, conf *bqConfig, headAid, tailAid int) (
4047 if err := am .loadArenaIntoMemory (headAid ); err != nil {
4148 return nil , err
4249 }
50+
4351 if err := am .loadArenaIntoMemory (tailAid ); err != nil {
4452 return nil , err
4553 }
@@ -63,6 +71,12 @@ func (m *arenaManager) getArena(aid int) (*arena, error) {
6371 return aa , nil
6472 }
6573
74+ // before we get a new arena into memory, we need to ensure that after fetching
75+ // a new arena into memory, we do not cross the provided memory limit
76+ if err := m .ensureEnoughMem (); err != nil {
77+ return nil , err
78+ }
79+
6680 // otherwise, get arena into memory
6781 if err := m .loadArenaIntoMemory (aid ); err != nil {
6882 return nil , err
@@ -71,18 +85,103 @@ func (m *arenaManager) getArena(aid int) (*arena, error) {
7185 return m .arenaList [relAid ], nil
7286}
7387
88+ // ensureEnoughMem ensures that at least 1 new arena can be brought into memory
89+ // TODO: shrink arenaList
90+ func (m * arenaManager ) ensureEnoughMem () error {
91+ // Check whether head has moved and arenas can be unmpped
92+ // Remove all such arenas from memory, irrespectively
93+ headAid , _ := m .index .getHead ()
94+ for aid := m .baseAid ; aid < headAid ; aid ++ {
95+ if err := m .unloadArenaFromMemory (aid ); err != nil {
96+ return err
97+ }
98+
99+ // disk garbage collection
100+ if err := m .deleteArenaBackedFile (aid ); err != nil {
101+ return err
102+ }
103+ }
104+
105+ // if no limit on # of arenas, no need for eviction
106+ if m .conf .maxInMemArenas == 0 {
107+ return nil
108+ }
109+
110+ // Check whether an eviction is needed to begin with
111+ if m .inMemArenas < m .conf .maxInMemArenas {
112+ return nil
113+ }
114+
115+ // Start evicting from the arena just before the last arena that we have.
116+ // If message size > arena size, last arena may not always be the tail arena.
117+ // We always ensure that head and tail arenas are not evicted from memory.
118+ // Assuming m.conf.maxInMemArenas >= 3.
119+ // Simply iterate from the last arena until enough memory is
120+ // available for a new arena to be loaded into memory
121+ tailAid , _ := m .index .getTail ()
122+ curAid := m .baseAid + len (m .arenaList )
123+ for m .conf .maxInMemArenas - m .inMemArenas <= 0 {
124+ curAid --
125+
126+ if curAid < 0 {
127+ return errShouldNotReach
128+ }
129+
130+ if curAid == tailAid || curAid == headAid {
131+ continue
132+ }
133+
134+ if err := m .unloadArenaFromMemory (curAid ); err != nil {
135+ return err
136+ }
137+ }
138+
139+ return nil
140+ }
141+
74142// loadArenaIntoMemory will fetch the arena into memory
75143func (m * arenaManager ) loadArenaIntoMemory (aid int ) error {
144+ if m .arenaList [aid - m .baseAid ] != nil {
145+ return nil
146+ }
147+
76148 filePath := path .Join (m .dir , fmt .Sprintf (cArenaFileFmt , aid ))
77149 aa , err := newArena (filePath , m .conf .arenaSize )
78150 if err != nil {
79151 return err
80152 }
81153
154+ m .inMemArenas ++
82155 m .arenaList [aid - m .baseAid ] = aa
83156 return nil
84157}
85158
159+ // unloadArenaFromMemory will remove the arena from memory
160+ func (m * arenaManager ) unloadArenaFromMemory (aid int ) error {
161+ if m .arenaList [aid - m .baseAid ] == nil {
162+ return nil
163+ }
164+
165+ if err := m .arenaList [aid - m .baseAid ].Unmap (); err != nil {
166+ return err
167+ }
168+
169+ m .inMemArenas --
170+ m .arenaList [aid - m .baseAid ] = nil
171+ return nil
172+ }
173+
174+ // deleteArenaBackedFile deletes the backed file for given arena with
175+ // arena id: aid. If file doesn't exist, the error is ignored.
176+ func (m * arenaManager ) deleteArenaBackedFile (aid int ) error {
177+ filePath := path .Join (m .dir , fmt .Sprintf (cArenaFileFmt , aid ))
178+ if err := os .Remove (filePath ); err != nil && ! os .IsNotExist (err ) {
179+ return err
180+ }
181+
182+ return nil
183+ }
184+
86185// close unmaps all the arenas managed by arenaManager
87186func (m * arenaManager ) close () error {
88187 var retErr error
0 commit comments