Skip to content

Commit e389abd

Browse files
authored
Free memory on request finish (#240)
* refactor(asyncloader): free memory on request finish * test(responsecache): test free behavior * style(asyncloader): cleanup code to check error * style(responsecache): add explanatory comment * fix(asyncloader): only free when there are blocks left to free
1 parent 9c5c74c commit e389abd

File tree

9 files changed

+36
-21
lines changed

9 files changed

+36
-21
lines changed

requestmanager/asyncloader/asyncloader.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -161,16 +161,22 @@ func (al *AsyncLoader) CompleteResponsesFor(requestID graphsync.RequestID) {
161161
// CleanupRequest indicates the given request is complete on the client side,
162162
// and no further attempts will be made to load links for this request,
163163
// so any cached response data is invalid can be cleaned
164-
func (al *AsyncLoader) CleanupRequest(requestID graphsync.RequestID) {
164+
func (al *AsyncLoader) CleanupRequest(p peer.ID, requestID graphsync.RequestID) {
165165
al.stateLk.Lock()
166166
defer al.stateLk.Unlock()
167+
responseCache := al.responseCache
167168
aq, ok := al.requestQueues[requestID]
168169
if ok {
169-
al.alternateQueues[aq].responseCache.FinishRequest(requestID)
170+
responseCache = al.alternateQueues[aq].responseCache
170171
delete(al.requestQueues, requestID)
171-
return
172172
}
173-
al.responseCache.FinishRequest(requestID)
173+
toFree := responseCache.FinishRequest(requestID)
174+
if toFree > 0 {
175+
err := al.allocator.ReleaseBlockMemory(p, toFree)
176+
if err != nil {
177+
log.Infow("Error deallocating requestor memory", "p", p, "toFree", toFree, "err", err)
178+
}
179+
}
174180
}
175181

176182
func (al *AsyncLoader) getLoadAttemptQueue(queue string) *loadattemptqueue.LoadAttemptQueue {

requestmanager/asyncloader/asyncloader_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ func TestRegisterUnregister(t *testing.T) {
219219
err = asyncLoader.UnregisterPersistenceOption("other")
220220
require.EqualError(t, err, "cannot unregister while requests are in progress")
221221
asyncLoader.CompleteResponsesFor(requestID2)
222-
asyncLoader.CleanupRequest(requestID2)
222+
asyncLoader.CleanupRequest(p, requestID2)
223223
err = asyncLoader.UnregisterPersistenceOption("other")
224224
require.NoError(t, err)
225225

requestmanager/asyncloader/responsecache/responsecache.go

+12-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ var log = logging.Logger("graphsync")
1818
// UnverifiedBlockStore is an interface for storing blocks
1919
// as they come in and removing them as they are verified
2020
type UnverifiedBlockStore interface {
21-
PruneBlocks(func(ipld.Link) bool)
21+
PruneBlocks(func(ipld.Link, uint64) bool)
2222
PruneBlock(ipld.Link)
2323
VerifyBlock(ipld.Link, ipld.LinkContext) ([]byte, error)
2424
AddUnverifiedBlock(ipld.Link, []byte)
@@ -42,15 +42,22 @@ func New(unverifiedBlockStore UnverifiedBlockStore) *ResponseCache {
4242
}
4343

4444
// FinishRequest indicate there is no more need to track blocks tied to this
45-
// response
46-
func (rc *ResponseCache) FinishRequest(requestID graphsync.RequestID) {
45+
// response. It returns the total number of bytes in blocks that were being
46+
// tracked but are no longer in memory
47+
func (rc *ResponseCache) FinishRequest(requestID graphsync.RequestID) uint64 {
4748
rc.responseCacheLk.Lock()
4849
rc.linkTracker.FinishRequest(requestID)
4950

50-
rc.unverifiedBlockStore.PruneBlocks(func(link ipld.Link) bool {
51-
return rc.linkTracker.BlockRefCount(link) == 0
51+
toFree := uint64(0)
52+
rc.unverifiedBlockStore.PruneBlocks(func(link ipld.Link, amt uint64) bool {
53+
shouldPrune := rc.linkTracker.BlockRefCount(link) == 0
54+
if shouldPrune {
55+
toFree += amt
56+
}
57+
return shouldPrune
5258
})
5359
rc.responseCacheLk.Unlock()
60+
return toFree
5461
}
5562

5663
// AttemptLoad attempts to laod the given block from the cache

requestmanager/asyncloader/responsecache/responsecache_test.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ func (ubs *fakeUnverifiedBlockStore) AddUnverifiedBlock(lnk ipld.Link, data []by
2323
ubs.inMemoryBlocks[lnk] = data
2424
}
2525

26-
func (ubs *fakeUnverifiedBlockStore) PruneBlocks(shouldPrune func(ipld.Link) bool) {
27-
for link := range ubs.inMemoryBlocks {
28-
if shouldPrune(link) {
26+
func (ubs *fakeUnverifiedBlockStore) PruneBlocks(shouldPrune func(ipld.Link, uint64) bool) {
27+
for link, data := range ubs.inMemoryBlocks {
28+
if shouldPrune(link, uint64(len(data))) {
2929
delete(ubs.inMemoryBlocks, link)
3030
}
3131
}
@@ -134,14 +134,16 @@ func TestResponseCacheManagingLinks(t *testing.T) {
134134
require.NoError(t, err)
135135
require.Nil(t, data, "no data should be returned for unknown block")
136136

137-
responseCache.FinishRequest(requestID1)
137+
toFree := responseCache.FinishRequest(requestID1)
138138
// should remove only block 0, since it now has no refering outstanding requests
139139
require.Len(t, fubs.blocks(), len(blks)-4, "should prune block when it is orphaned")
140140
testutil.RefuteContainsBlock(t, fubs.blocks(), blks[0])
141+
require.Equal(t, toFree, uint64(len(blks[0].RawData())))
141142

142143
responseCache.FinishRequest(requestID2)
143144
// should remove last block since are no remaining references
144145
require.Len(t, fubs.blocks(), 0, "should prune block when it is orphaned")
145146
testutil.RefuteContainsBlock(t, fubs.blocks(), blks[3])
147+
require.Equal(t, toFree, uint64(len(blks[3].RawData())))
146148

147149
}

requestmanager/asyncloader/unverifiedblockstore/unverifiedblockstore.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ func (ubs *UnverifiedBlockStore) AddUnverifiedBlock(lnk ipld.Link, data []byte)
4040

4141
// PruneBlocks removes blocks from the unverified store without committing them,
4242
// if the passed in function returns true for the given link
43-
func (ubs *UnverifiedBlockStore) PruneBlocks(shouldPrune func(ipld.Link) bool) {
43+
func (ubs *UnverifiedBlockStore) PruneBlocks(shouldPrune func(ipld.Link, uint64) bool) {
4444
for link, data := range ubs.inMemoryBlocks {
45-
if shouldPrune(link) {
45+
if shouldPrune(link, uint64(len(data))) {
4646
delete(ubs.inMemoryBlocks, link)
4747
ubs.dataSize = ubs.dataSize - uint64(len(data))
4848
}

requestmanager/client.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ type AsyncLoader interface {
8282
blks []blocks.Block)
8383
AsyncLoad(p peer.ID, requestID graphsync.RequestID, link ipld.Link, linkContext ipld.LinkContext) <-chan types.AsyncLoadResult
8484
CompleteResponsesFor(requestID graphsync.RequestID)
85-
CleanupRequest(requestID graphsync.RequestID)
85+
CleanupRequest(p peer.ID, requestID graphsync.RequestID)
8686
}
8787

8888
// RequestManager tracks outgoing requests and processes incoming reponses

requestmanager/requestmanager_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@ func TestPauseResume(t *testing.T) {
813813
// verify no further responses come through
814814
time.Sleep(100 * time.Millisecond)
815815
testutil.AssertChannelEmpty(t, returnedResponseChan, "no response should be sent request is paused")
816-
td.fal.CleanupRequest(rr.gsr.ID())
816+
td.fal.CleanupRequest(peers[0], rr.gsr.ID())
817817

818818
// unpause
819819
err = td.requestManager.UnpauseRequest(rr.gsr.ID(), td.extension1, td.extension2)
@@ -893,7 +893,7 @@ func TestPauseResumeExternal(t *testing.T) {
893893
// verify no further responses come through
894894
time.Sleep(100 * time.Millisecond)
895895
testutil.AssertChannelEmpty(t, returnedResponseChan, "no response should be sent request is paused")
896-
td.fal.CleanupRequest(rr.gsr.ID())
896+
td.fal.CleanupRequest(peers[0], rr.gsr.ID())
897897

898898
// unpause
899899
err = td.requestManager.UnpauseRequest(rr.gsr.ID(), td.extension1, td.extension2)

requestmanager/server.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func (rm *RequestManager) terminateRequest(requestID graphsync.RequestID, ipr *i
168168
rm.connManager.Unprotect(ipr.p, requestID.Tag())
169169
delete(rm.inProgressRequestStatuses, requestID)
170170
ipr.cancelFn()
171-
rm.asyncLoader.CleanupRequest(requestID)
171+
rm.asyncLoader.CleanupRequest(ipr.p, requestID)
172172
if ipr.traverser != nil {
173173
ipr.traverserCancel()
174174
ipr.traverser.Shutdown(rm.ctx)

requestmanager/testloader/asyncloader.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func (fal *FakeAsyncLoader) CompleteResponsesFor(requestID graphsync.RequestID)
132132

133133
// CleanupRequest simulates the effect of cleaning up the request by removing any response channels
134134
// for the request
135-
func (fal *FakeAsyncLoader) CleanupRequest(requestID graphsync.RequestID) {
135+
func (fal *FakeAsyncLoader) CleanupRequest(p peer.ID, requestID graphsync.RequestID) {
136136
fal.responseChannelsLk.Lock()
137137
for key := range fal.responseChannels {
138138
if key.requestID == requestID {

0 commit comments

Comments
 (0)