duckdb 0.5.2-dev1809.0 → 0.5.2-dev1819.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "duckdb",
3
3
  "main": "./lib/duckdb.js",
4
4
  "types": "./lib/duckdb.d.ts",
5
- "version": "0.5.2-dev1809.0",
5
+ "version": "0.5.2-dev1819.0",
6
6
  "description": "DuckDB node.js API",
7
7
  "gypfile": true,
8
8
  "dependencies": {
package/src/duckdb.cpp CHANGED
@@ -17105,32 +17105,32 @@ void FileBuffer::ReallocBuffer(size_t new_size) {
17105
17105
  size = 0;
17106
17106
  }
17107
17107
 
17108
- void FileBuffer::Resize(uint64_t new_size) {
17109
- idx_t header_size = Storage::BLOCK_HEADER_SIZE;
17110
- {
17111
- // TODO: All the logic here is specific to SingleFileBlockManager.
17112
- // and should be moved there, via a specific implementation of FileBuffer.
17113
- //
17114
- // make room for the block header (if this is not the db file header)
17115
- if (type == FileBufferType::TINY_BUFFER) {
17116
- header_size = 0;
17117
- }
17118
- if (type == FileBufferType::MANAGED_BUFFER) {
17119
- new_size += Storage::BLOCK_HEADER_SIZE;
17120
- }
17121
- if (type != FileBufferType::TINY_BUFFER) {
17122
- new_size = AlignValue<uint32_t, Storage::SECTOR_SIZE>(new_size);
17123
- }
17124
- ReallocBuffer(new_size);
17108
+ FileBuffer::MemoryRequirement FileBuffer::CalculateMemory(uint64_t user_size) {
17109
+ FileBuffer::MemoryRequirement result;
17110
+
17111
+ if (type == FileBufferType::TINY_BUFFER) {
17112
+ // We never do IO on tiny buffers, so there's no need to add a header or sector-align.
17113
+ result.header_size = 0;
17114
+ result.alloc_size = user_size;
17115
+ } else {
17116
+ result.header_size = Storage::BLOCK_HEADER_SIZE;
17117
+ result.alloc_size = AlignValue<uint32_t, Storage::SECTOR_SIZE>(result.header_size + user_size);
17125
17118
  }
17119
+ return result;
17120
+ }
17121
+
17122
+ void FileBuffer::Resize(uint64_t new_size) {
17123
+ auto req = CalculateMemory(new_size);
17124
+ ReallocBuffer(req.alloc_size);
17126
17125
 
17127
17126
  if (new_size > 0) {
17128
- buffer = internal_buffer + header_size;
17129
- size = internal_size - header_size;
17127
+ buffer = internal_buffer + req.header_size;
17128
+ size = internal_size - req.header_size;
17130
17129
  }
17131
17130
  }
17132
17131
 
17133
17132
  void FileBuffer::Read(FileHandle &handle, uint64_t location) {
17133
+ D_ASSERT(type != FileBufferType::TINY_BUFFER);
17134
17134
  handle.Read(internal_buffer, internal_size, location);
17135
17135
  }
17136
17136
 
@@ -17148,6 +17148,7 @@ void FileBuffer::ReadAndChecksum(FileHandle &handle, uint64_t location) {
17148
17148
  }
17149
17149
 
17150
17150
  void FileBuffer::Write(FileHandle &handle, uint64_t location) {
17151
+ D_ASSERT(type != FileBufferType::TINY_BUFFER);
17151
17152
  handle.Write(internal_buffer, internal_size, location);
17152
17153
  }
17153
17154
 
@@ -199118,6 +199119,33 @@ public:
199118
199119
 
199119
199120
  namespace duckdb {
199120
199121
 
199122
+ BufferPoolReservation::BufferPoolReservation(BufferPoolReservation &&src) noexcept {
199123
+ size = src.size;
199124
+ src.size = 0;
199125
+ }
199126
+
199127
+ BufferPoolReservation &BufferPoolReservation::operator=(BufferPoolReservation &&src) noexcept {
199128
+ size = src.size;
199129
+ src.size = 0;
199130
+ return *this;
199131
+ }
199132
+
199133
+ BufferPoolReservation::~BufferPoolReservation() {
199134
+ D_ASSERT(size == 0);
199135
+ }
199136
+
199137
+ void BufferPoolReservation::Resize(atomic<idx_t> &counter, idx_t new_size) {
199138
+ int64_t delta = (int64_t)new_size - size;
199139
+ D_ASSERT(delta > 0 || (int64_t)counter >= -delta);
199140
+ counter += delta;
199141
+ size = new_size;
199142
+ }
199143
+
199144
+ void BufferPoolReservation::Merge(BufferPoolReservation &&src) {
199145
+ size += src.size;
199146
+ src.size = 0;
199147
+ }
199148
+
199121
199149
  struct BufferAllocatorData : PrivateAllocatorData {
199122
199150
  explicit BufferAllocatorData(BufferManager &manager) : manager(manager) {
199123
199151
  }
@@ -199134,12 +199162,13 @@ BlockHandle::BlockHandle(BlockManager &block_manager, block_id_t block_id_p)
199134
199162
  }
199135
199163
 
199136
199164
  BlockHandle::BlockHandle(BlockManager &block_manager, block_id_t block_id_p, unique_ptr<FileBuffer> buffer_p,
199137
- bool can_destroy_p, idx_t block_size)
199165
+ bool can_destroy_p, idx_t block_size, BufferPoolReservation &&reservation)
199138
199166
  : block_manager(block_manager), readers(0), block_id(block_id_p), eviction_timestamp(0), can_destroy(can_destroy_p),
199139
199167
  unswizzled(nullptr) {
199140
199168
  buffer = move(buffer_p);
199141
199169
  state = BlockState::BLOCK_LOADED;
199142
- memory_usage = buffer->type == FileBufferType::TINY_BUFFER ? block_size : block_size + Storage::BLOCK_HEADER_SIZE;
199170
+ memory_usage = buffer->AllocSize();
199171
+ memory_charge = move(reservation);
199143
199172
  }
199144
199173
 
199145
199174
  BlockHandle::~BlockHandle() {
@@ -199148,10 +199177,12 @@ BlockHandle::~BlockHandle() {
199148
199177
  auto &buffer_manager = block_manager.buffer_manager;
199149
199178
  // no references remain to this block: erase
199150
199179
  if (buffer && state == BlockState::BLOCK_LOADED) {
199180
+ D_ASSERT(memory_charge.size > 0);
199151
199181
  // the block is still loaded in memory: erase it
199152
199182
  buffer.reset();
199153
- D_ASSERT(buffer_manager.current_memory >= memory_usage);
199154
- buffer_manager.current_memory -= memory_usage;
199183
+ memory_charge.Resize(buffer_manager.current_memory, 0);
199184
+ } else {
199185
+ D_ASSERT(memory_charge.size == 0);
199155
199186
  }
199156
199187
  block_manager.UnregisterBlock(block_id, can_destroy);
199157
199188
  }
@@ -199220,10 +199251,9 @@ unique_ptr<FileBuffer> BlockHandle::UnloadAndTakeBlock() {
199220
199251
 
199221
199252
  if (block_id >= MAXIMUM_BLOCK && !can_destroy) {
199222
199253
  // temporary block that cannot be destroyed: write to temporary file
199223
- D_ASSERT(memory_usage >= Storage::BLOCK_ALLOC_SIZE);
199224
199254
  block_manager.buffer_manager.WriteTemporaryBuffer(block_id, *buffer);
199225
199255
  }
199226
- block_manager.buffer_manager.current_memory -= memory_usage;
199256
+ memory_charge.Resize(block_manager.buffer_manager.current_memory, 0);
199227
199257
  state = BlockState::BLOCK_UNLOADED;
199228
199258
  return move(buffer);
199229
199259
  }
@@ -199362,6 +199392,8 @@ shared_ptr<BlockHandle> BlockManager::ConvertToPersistent(block_id_t block_id, s
199362
199392
  // move the data from the old block into data for the new block
199363
199393
  new_block->state = BlockState::BLOCK_LOADED;
199364
199394
  new_block->buffer = CreateBlock(block_id, old_block->buffer.get());
199395
+ new_block->memory_usage = old_block->memory_usage;
199396
+ new_block->memory_charge = move(old_block->memory_charge);
199365
199397
 
199366
199398
  // clear the old buffer and unload it
199367
199399
  old_block->buffer.reset();
@@ -199378,15 +199410,25 @@ shared_ptr<BlockHandle> BlockManager::ConvertToPersistent(block_id_t block_id, s
199378
199410
  return new_block;
199379
199411
  }
199380
199412
 
199381
- shared_ptr<BlockHandle> BufferManager::RegisterSmallMemory(idx_t block_size) {
199382
- if (!EvictBlocks(block_size, maximum_memory, nullptr)) {
199383
- throw OutOfMemoryException("could not allocate block of %lld bytes (%lld/%lld used) %s", block_size,
199384
- GetUsedMemory(), GetMaxMemory(), InMemoryWarning());
199413
+ template <typename... ARGS>
199414
+ TempBufferPoolReservation BufferManager::EvictBlocksOrThrow(idx_t memory_delta, idx_t limit,
199415
+ unique_ptr<FileBuffer> *buffer, ARGS... args) {
199416
+ auto r = EvictBlocks(memory_delta, limit, buffer);
199417
+ if (!r.success) {
199418
+ throw OutOfMemoryException(args..., InMemoryWarning());
199385
199419
  }
199420
+ return move(r.reservation);
199421
+ }
199422
+
199423
+ shared_ptr<BlockHandle> BufferManager::RegisterSmallMemory(idx_t block_size) {
199424
+ auto res = EvictBlocksOrThrow(block_size, maximum_memory, nullptr,
199425
+ "could not allocate block of %lld bytes (%lld/%lld used) %s", block_size,
199426
+ GetUsedMemory(), GetMaxMemory());
199427
+
199386
199428
  auto buffer = ConstructManagedBuffer(block_size, nullptr, FileBufferType::TINY_BUFFER);
199387
199429
 
199388
199430
  // create a new block pointer for this block
199389
- return make_shared<BlockHandle>(*temp_block_manager, ++temporary_id, move(buffer), false, block_size);
199431
+ return make_shared<BlockHandle>(*temp_block_manager, ++temporary_id, move(buffer), false, block_size, move(res));
199390
199432
  }
199391
199433
 
199392
199434
  shared_ptr<BlockHandle> BufferManager::RegisterMemory(idx_t block_size, bool can_destroy) {
@@ -199394,15 +199436,15 @@ shared_ptr<BlockHandle> BufferManager::RegisterMemory(idx_t block_size, bool can
199394
199436
  auto alloc_size = AlignValue<idx_t, Storage::SECTOR_SIZE>(block_size + Storage::BLOCK_HEADER_SIZE);
199395
199437
  // first evict blocks until we have enough memory to store this buffer
199396
199438
  unique_ptr<FileBuffer> reusable_buffer;
199397
- if (!EvictBlocks(alloc_size, maximum_memory, &reusable_buffer)) {
199398
- throw OutOfMemoryException("could not allocate block of %lld bytes (%lld/%lld used) %s", alloc_size,
199399
- GetUsedMemory(), GetMaxMemory(), InMemoryWarning());
199400
- }
199439
+ auto res = EvictBlocksOrThrow(alloc_size, maximum_memory, &reusable_buffer,
199440
+ "could not allocate block of %lld bytes (%lld/%lld used) %s", alloc_size,
199441
+ GetUsedMemory(), GetMaxMemory());
199401
199442
 
199402
199443
  auto buffer = ConstructManagedBuffer(block_size, move(reusable_buffer));
199403
199444
 
199404
199445
  // create a new block pointer for this block
199405
- return make_shared<BlockHandle>(*temp_block_manager, ++temporary_id, move(buffer), can_destroy, block_size);
199446
+ return make_shared<BlockHandle>(*temp_block_manager, ++temporary_id, move(buffer), can_destroy, block_size,
199447
+ move(res));
199406
199448
  }
199407
199449
 
199408
199450
  BufferHandle BufferManager::Allocate(idx_t block_size) {
@@ -199414,25 +199456,29 @@ void BufferManager::ReAllocate(shared_ptr<BlockHandle> &handle, idx_t block_size
199414
199456
  D_ASSERT(block_size >= Storage::BLOCK_SIZE);
199415
199457
  lock_guard<mutex> lock(handle->lock);
199416
199458
  D_ASSERT(handle->state == BlockState::BLOCK_LOADED);
199417
- auto alloc_size = block_size + Storage::BLOCK_HEADER_SIZE;
199418
- int64_t required_memory = alloc_size - handle->memory_usage;
199419
- if (required_memory == 0) {
199459
+ D_ASSERT(handle->memory_usage == handle->buffer->AllocSize());
199460
+ D_ASSERT(handle->memory_usage == handle->memory_charge.size);
199461
+
199462
+ auto req = handle->buffer->CalculateMemory(block_size);
199463
+ int64_t memory_delta = (int64_t)req.alloc_size - handle->memory_usage;
199464
+
199465
+ if (memory_delta == 0) {
199420
199466
  return;
199421
- } else if (required_memory > 0) {
199467
+ } else if (memory_delta > 0) {
199422
199468
  // evict blocks until we have space to resize this block
199423
- if (!EvictBlocks(required_memory, maximum_memory)) {
199424
- throw OutOfMemoryException("failed to resize block from %lld to %lld%s", handle->memory_usage, alloc_size,
199425
- InMemoryWarning());
199426
- }
199469
+ auto reservation =
199470
+ EvictBlocksOrThrow(memory_delta, maximum_memory, nullptr, "failed to resize block from %lld to %lld%s",
199471
+ handle->memory_usage, req.alloc_size);
199472
+ // EvictBlocks decrements 'current_memory' for us.
199473
+ handle->memory_charge.Merge(move(reservation));
199427
199474
  } else {
199428
- // no need to evict blocks
199429
- D_ASSERT(current_memory >= idx_t(-required_memory));
199430
- current_memory -= idx_t(-required_memory);
199475
+ // no need to evict blocks, but we do need to decrement 'current_memory'.
199476
+ handle->memory_charge.Resize(current_memory, req.alloc_size);
199431
199477
  }
199432
199478
 
199433
199479
  // resize and adjust current memory
199434
199480
  handle->buffer->Resize(block_size);
199435
- handle->memory_usage = alloc_size;
199481
+ handle->memory_usage += memory_delta;
199436
199482
  }
199437
199483
 
199438
199484
  BufferHandle BufferManager::Pin(shared_ptr<BlockHandle> &handle) {
@@ -199448,32 +199494,43 @@ BufferHandle BufferManager::Pin(shared_ptr<BlockHandle> &handle) {
199448
199494
  }
199449
199495
  required_memory = handle->memory_usage;
199450
199496
  }
199451
- D_ASSERT(required_memory >= Storage::BLOCK_SIZE);
199452
199497
  // evict blocks until we have space for the current block
199453
199498
  unique_ptr<FileBuffer> reusable_buffer;
199454
- if (!EvictBlocks(required_memory, maximum_memory, &reusable_buffer)) {
199455
- throw OutOfMemoryException("failed to pin block of size %lld%s", required_memory, InMemoryWarning());
199456
- }
199499
+ auto reservation = EvictBlocksOrThrow(required_memory, maximum_memory, &reusable_buffer,
199500
+ "failed to pin block of size %lld%s", required_memory);
199457
199501
  // lock the handle again and repeat the check (in case anybody loaded in the mean time)
199458
199502
  lock_guard<mutex> lock(handle->lock);
199459
199503
  // check if the block is already loaded
199460
199504
  if (handle->state == BlockState::BLOCK_LOADED) {
199461
199505
  // the block is loaded, increment the reader count and return a pointer to the handle
199462
199506
  handle->readers++;
199463
- D_ASSERT(current_memory >= required_memory);
199464
- current_memory -= required_memory;
199507
+ reservation.Resize(current_memory, 0);
199465
199508
  return handle->Load(handle);
199466
199509
  }
199467
199510
  // now we can actually load the current block
199468
199511
  D_ASSERT(handle->readers == 0);
199469
199512
  handle->readers = 1;
199470
- return handle->Load(handle, move(reusable_buffer));
199513
+ auto buf = handle->Load(handle, move(reusable_buffer));
199514
+ handle->memory_charge = move(reservation);
199515
+ // In the case of a variable sized block, the buffer may be smaller than a full block.
199516
+ int64_t delta = handle->buffer->AllocSize() - handle->memory_usage;
199517
+ if (delta) {
199518
+ D_ASSERT(delta < 0);
199519
+ handle->memory_usage += delta;
199520
+ handle->memory_charge.Resize(current_memory, handle->memory_usage);
199521
+ }
199522
+ return buf;
199471
199523
  }
199472
199524
 
199473
199525
  void BufferManager::AddToEvictionQueue(shared_ptr<BlockHandle> &handle) {
199526
+ constexpr int INSERT_INTERVAL = 1024;
199527
+
199474
199528
  D_ASSERT(handle->readers == 0);
199475
199529
  handle->eviction_timestamp++;
199476
- PurgeQueue();
199530
+ // After each 1024 insertions, run through the queue and purge.
199531
+ if ((++queue_insertions % INSERT_INTERVAL) == 0) {
199532
+ PurgeQueue();
199533
+ }
199477
199534
  queue->q.enqueue(BufferEvictionNode(weak_ptr<BlockHandle>(handle), handle->eviction_timestamp));
199478
199535
  }
199479
199536
 
@@ -199489,17 +199546,16 @@ void BufferManager::Unpin(shared_ptr<BlockHandle> &handle) {
199489
199546
  }
199490
199547
  }
199491
199548
 
199492
- bool BufferManager::EvictBlocks(idx_t extra_memory, idx_t memory_limit, unique_ptr<FileBuffer> *buffer) {
199493
- PurgeQueue();
199494
-
199549
+ BufferManager::EvictionResult BufferManager::EvictBlocks(idx_t extra_memory, idx_t memory_limit,
199550
+ unique_ptr<FileBuffer> *buffer) {
199495
199551
  BufferEvictionNode node;
199496
- current_memory += extra_memory;
199552
+ TempBufferPoolReservation r(current_memory, extra_memory);
199497
199553
  while (current_memory > memory_limit) {
199498
199554
  // get a block to unpin from the queue
199499
199555
  if (!queue->q.try_dequeue(node)) {
199500
- D_ASSERT(current_memory >= extra_memory);
199501
- current_memory -= extra_memory;
199502
- return false;
199556
+ // Failed to reserve. Adjust size of temp reservation to 0.
199557
+ r.Resize(current_memory, 0);
199558
+ return {false, move(r)};
199503
199559
  }
199504
199560
  // get a reference to the underlying block pointer
199505
199561
  auto handle = node.TryGetBlockHandle();
@@ -199516,13 +199572,13 @@ bool BufferManager::EvictBlocks(idx_t extra_memory, idx_t memory_limit, unique_p
199516
199572
  if (buffer && handle->buffer->AllocSize() == extra_memory) {
199517
199573
  // we can actually re-use the memory directly!
199518
199574
  *buffer = handle->UnloadAndTakeBlock();
199519
- return true;
199575
+ return {true, move(r)};
199520
199576
  } else {
199521
199577
  // release the memory and mark the block as unloaded
199522
199578
  handle->Unload();
199523
199579
  }
199524
199580
  }
199525
- return true;
199581
+ return {true, move(r)};
199526
199582
  }
199527
199583
 
199528
199584
  void BufferManager::PurgeQueue() {
@@ -199558,7 +199614,7 @@ void BlockManager::UnregisterBlock(block_id_t block_id, bool can_destroy) {
199558
199614
  void BufferManager::SetLimit(idx_t limit) {
199559
199615
  lock_guard<mutex> l_lock(limit_lock);
199560
199616
  // try to evict until the limit is reached
199561
- if (!EvictBlocks(0, limit)) {
199617
+ if (!EvictBlocks(0, limit).success) {
199562
199618
  throw OutOfMemoryException(
199563
199619
  "Failed to change memory limit to %lld: could not free up enough memory for the new limit%s", limit,
199564
199620
  InMemoryWarning());
@@ -199567,7 +199623,7 @@ void BufferManager::SetLimit(idx_t limit) {
199567
199623
  // set the global maximum memory to the new limit if successful
199568
199624
  maximum_memory = limit;
199569
199625
  // evict again
199570
- if (!EvictBlocks(0, limit)) {
199626
+ if (!EvictBlocks(0, limit).success) {
199571
199627
  // failed: go back to old limit
199572
199628
  maximum_memory = old_limit;
199573
199629
  throw OutOfMemoryException(
@@ -199987,24 +200043,28 @@ string BufferManager::InMemoryWarning() {
199987
200043
  //===--------------------------------------------------------------------===//
199988
200044
  data_ptr_t BufferManager::BufferAllocatorAllocate(PrivateAllocatorData *private_data, idx_t size) {
199989
200045
  auto &data = (BufferAllocatorData &)*private_data;
199990
- if (!data.manager.EvictBlocks(size, data.manager.maximum_memory)) {
199991
- throw OutOfMemoryException("failed to allocate data of size %lld%s", size, data.manager.InMemoryWarning());
199992
- }
200046
+ auto reservation = data.manager.EvictBlocksOrThrow(size, data.manager.maximum_memory, nullptr,
200047
+ "failed to allocate data of size %lld%s", size);
200048
+ // We rely on manual tracking of this one. :(
200049
+ reservation.size = 0;
199993
200050
  return Allocator::Get(data.manager.db).AllocateData(size);
199994
200051
  }
199995
200052
 
199996
200053
  void BufferManager::BufferAllocatorFree(PrivateAllocatorData *private_data, data_ptr_t pointer, idx_t size) {
199997
200054
  auto &data = (BufferAllocatorData &)*private_data;
199998
- D_ASSERT(data.manager.current_memory >= size);
199999
- data.manager.current_memory -= size;
200055
+ BufferPoolReservation r;
200056
+ r.size = size;
200057
+ r.Resize(data.manager.current_memory, 0);
200000
200058
  return Allocator::Get(data.manager.db).FreeData(pointer, size);
200001
200059
  }
200002
200060
 
200003
200061
  data_ptr_t BufferManager::BufferAllocatorRealloc(PrivateAllocatorData *private_data, data_ptr_t pointer, idx_t old_size,
200004
200062
  idx_t size) {
200005
200063
  auto &data = (BufferAllocatorData &)*private_data;
200006
- data.manager.current_memory -= old_size;
200007
- data.manager.current_memory += size;
200064
+ BufferPoolReservation r;
200065
+ r.size = old_size;
200066
+ r.Resize(data.manager.current_memory, size);
200067
+ r.size = 0;
200008
200068
  return Allocator::Get(data.manager.db).ReallocateData(pointer, old_size, size);
200009
200069
  }
200010
200070
 
package/src/duckdb.hpp CHANGED
@@ -11,8 +11,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
11
11
  #pragma once
12
12
  #define DUCKDB_AMALGAMATION 1
13
13
  #define DUCKDB_AMALGAMATION_EXTENDED 1
14
- #define DUCKDB_SOURCE_ID "92c7d9246d"
15
- #define DUCKDB_VERSION "v0.5.2-dev1809"
14
+ #define DUCKDB_SOURCE_ID "bf5b49fcee"
15
+ #define DUCKDB_VERSION "v0.5.2-dev1819"
16
16
  //===----------------------------------------------------------------------===//
17
17
  // DuckDB
18
18
  //
@@ -1980,12 +1980,19 @@ public:
1980
1980
 
1981
1981
  // Same rules as the constructor. We will add room for a header, in additio to
1982
1982
  // the requested user bytes. We will then sector-align the result.
1983
- virtual void Resize(uint64_t user_size);
1983
+ void Resize(uint64_t user_size);
1984
1984
 
1985
1985
  uint64_t AllocSize() const {
1986
1986
  return internal_size;
1987
1987
  }
1988
1988
 
1989
+ struct MemoryRequirement {
1990
+ idx_t alloc_size;
1991
+ idx_t header_size;
1992
+ };
1993
+
1994
+ MemoryRequirement CalculateMemory(uint64_t user_size);
1995
+
1989
1996
  protected:
1990
1997
  //! The pointer to the internal buffer that will be read or written, including the buffer header
1991
1998
  data_ptr_t internal_buffer;
@@ -25280,6 +25287,34 @@ class FileBuffer;
25280
25287
 
25281
25288
  enum class BlockState : uint8_t { BLOCK_UNLOADED = 0, BLOCK_LOADED = 1 };
25282
25289
 
25290
+ struct BufferPoolReservation {
25291
+ idx_t size {0};
25292
+
25293
+ BufferPoolReservation() {
25294
+ }
25295
+ BufferPoolReservation(const BufferPoolReservation &) = delete;
25296
+ BufferPoolReservation &operator=(const BufferPoolReservation &) = delete;
25297
+
25298
+ BufferPoolReservation(BufferPoolReservation &&) noexcept;
25299
+ BufferPoolReservation &operator=(BufferPoolReservation &&) noexcept;
25300
+
25301
+ ~BufferPoolReservation();
25302
+
25303
+ void Resize(atomic<idx_t> &counter, idx_t new_size);
25304
+ void Merge(BufferPoolReservation &&src);
25305
+ };
25306
+
25307
+ struct TempBufferPoolReservation : BufferPoolReservation {
25308
+ atomic<idx_t> &counter;
25309
+ TempBufferPoolReservation(atomic<idx_t> &counter, idx_t size) : counter(counter) {
25310
+ Resize(counter, size);
25311
+ }
25312
+ TempBufferPoolReservation(TempBufferPoolReservation &&) = default;
25313
+ ~TempBufferPoolReservation() {
25314
+ Resize(counter, 0);
25315
+ }
25316
+ };
25317
+
25283
25318
  class BlockHandle {
25284
25319
  friend class BlockManager;
25285
25320
  friend struct BufferEvictionNode;
@@ -25289,7 +25324,7 @@ class BlockHandle {
25289
25324
  public:
25290
25325
  BlockHandle(BlockManager &block_manager, block_id_t block_id);
25291
25326
  BlockHandle(BlockManager &block_manager, block_id_t block_id, unique_ptr<FileBuffer> buffer, bool can_destroy,
25292
- idx_t block_size);
25327
+ idx_t block_size, BufferPoolReservation &&reservation);
25293
25328
  ~BlockHandle();
25294
25329
 
25295
25330
  BlockManager &block_manager;
@@ -25331,8 +25366,11 @@ private:
25331
25366
  atomic<idx_t> eviction_timestamp;
25332
25367
  //! Whether or not the buffer can be destroyed (only used for temporary buffers)
25333
25368
  const bool can_destroy;
25334
- //! The memory usage of the block
25369
+ //! The memory usage of the block (when loaded). If we are pinning/loading
25370
+ //! an unloaded block, this tells us how much memory to reserve.
25335
25371
  idx_t memory_usage;
25372
+ //! Current memory reservation / usage
25373
+ BufferPoolReservation memory_charge;
25336
25374
  //! Does the block contain any memory pointers?
25337
25375
  const char *unswizzled;
25338
25376
  };
@@ -25418,7 +25456,18 @@ private:
25418
25456
  //! (i.e. not enough blocks could be evicted)
25419
25457
  //! If the "buffer" argument is specified AND the system can find a buffer to re-use for the given allocation size
25420
25458
  //! "buffer" will be made to point to the re-usable memory. Note that this is not guaranteed.
25421
- bool EvictBlocks(idx_t extra_memory, idx_t memory_limit, unique_ptr<FileBuffer> *buffer = nullptr);
25459
+ //! Returns a pair. result.first indicates if eviction was successful. result.second contains the
25460
+ //! reservation handle, which can be moved to the BlockHandle that will own the reservation.
25461
+ struct EvictionResult {
25462
+ bool success;
25463
+ TempBufferPoolReservation reservation;
25464
+ };
25465
+ EvictionResult EvictBlocks(idx_t extra_memory, idx_t memory_limit, unique_ptr<FileBuffer> *buffer = nullptr);
25466
+
25467
+ //! Helper
25468
+ template <typename... ARGS>
25469
+ TempBufferPoolReservation EvictBlocksOrThrow(idx_t extra_memory, idx_t limit, unique_ptr<FileBuffer> *buffer,
25470
+ ARGS...);
25422
25471
 
25423
25472
  //! Garbage collect eviction queue
25424
25473
  void PurgeQueue();
@@ -25462,6 +25511,8 @@ private:
25462
25511
  unique_ptr<EvictionQueue> queue;
25463
25512
  //! The temporary id used for managed buffers
25464
25513
  atomic<block_id_t> temporary_id;
25514
+ //! Total number of insertions into the eviction queue. This guides the schedule for calling PurgeQueue.
25515
+ atomic<uint32_t> queue_insertions;
25465
25516
  //! Allocator associated with the buffer manager, that passes all allocations through this buffer manager
25466
25517
  Allocator buffer_allocator;
25467
25518
  //! Block manager for temp data