summaryrefslogtreecommitdiff
path: root/src/cache
diff options
context:
space:
mode:
Diffstat (limited to 'src/cache')
-rw-r--r--src/cache/backing.h13
-rw-r--r--src/cache/backings/file.c27
-rw-r--r--src/cache/backings/file.h4
-rw-r--r--src/cache/backings/file_common.c1
-rw-r--r--src/cache/backings/file_mapped.c39
-rw-r--r--src/cache/backings/memory.c27
-rw-r--r--src/cache/entry.c15
-rw-r--r--src/cache/entry.h9
-rw-r--r--src/cache/handle.c37
-rw-r--r--src/cache/handle.h2
-rw-r--r--src/cache/handler.h13
-rw-r--r--src/cache/handlers/cdio.c206
-rw-r--r--src/cache/handlers/cdio.h21
-rw-r--r--src/cache/handlers/file.c8
-rw-r--r--src/cache/handlers/file.h1
-rw-r--r--src/cache/handlers/http.c153
-rw-r--r--src/cache/handlers/http.h14
-rw-r--r--src/cache/meson.build2
-rw-r--r--src/cache/range.h23
-rw-r--r--src/cache/threaded_waits.c126
-rw-r--r--src/cache/threaded_waits.h15
-rw-r--r--src/cache/wait.h10
22 files changed, 567 insertions, 199 deletions
diff --git a/src/cache/backing.h b/src/cache/backing.h
index 24aff44..d9d8bad 100644
--- a/src/cache/backing.h
+++ b/src/cache/backing.h
@@ -1,19 +1,26 @@
#pragma once
#include <al/types.h>
+#include <al/array.h>
enum {
- CCH_BACKING_READ = 0,
- CCH_BACKING_MAPPED
+ CACHE_BACKING_READ = 0,
+ CACHE_BACKING_MAPPED
+};
+
+struct cch_range {
+ off_t start;
+ off_t end;
};
struct cch_backing {
u8 mode;
+ array(struct cch_range) available;
+ void (*lock)(struct cch_backing *);
void (*write)(struct cch_backing *, u8 *, off_t, size_t *);
void (*read)(struct cch_backing *, u8 *, off_t, size_t *);
u8 *(*get_ptr)(struct cch_backing *, off_t, size_t *);
void (*unlock)(struct cch_backing *);
- void *(*get_mmap)(struct cch_backing *);
off_t (*get_size_estimate)(struct cch_backing *);
void (*resize)(struct cch_backing *, size_t);
void (*free)(struct cch_backing **);
diff --git a/src/cache/backings/file.c b/src/cache/backings/file.c
index 4c5f344..153b1e4 100644
--- a/src/cache/backings/file.c
+++ b/src/cache/backings/file.c
@@ -1,6 +1,14 @@
+#include "../range.h"
+
#include "file.h"
#include "file_common.c"
+static void file_backing_lock(struct cch_backing *backing)
+{
+ struct cch_backing_file *file = (struct cch_backing_file *)backing;
+ aki_mutex_lock(&file->mutex);
+}
+
static void file_backing_write(struct cch_backing *backing, u8 *buf, off_t index, size_t *size)
{
struct cch_backing_file *file = (struct cch_backing_file *)backing;
@@ -10,10 +18,11 @@ static void file_backing_write(struct cch_backing *backing, u8 *buf, off_t index
al_assert(file->u.pointer == index);
}
if (file->size >= 0 && index + ((off_t)*size) >= file->size) {
- *size = file->size - index;
+ *size = AL_MAX(file->size - index, 0);
}
aki_file_write(&file->file, buf, *size);
file->u.pointer += *size;
+ cch_backing_fill_range(&file->backing, index, *size);
aki_mutex_unlock(&file->mutex);
}
@@ -26,13 +35,19 @@ static void file_backing_read(struct cch_backing *backing, u8 *buf, off_t index,
al_assert(file->u.pointer == index);
}
if (file->size >= 0 && index + ((off_t)*size) >= file->size) {
- *size = file->size - index;
+ *size = AL_MAX(file->size - index, 0);
}
aki_file_read(&file->file, buf, *size);
file->u.pointer += *size;
aki_mutex_unlock(&file->mutex);
}
+static void file_backing_unlock(struct cch_backing *backing)
+{
+ struct cch_backing_file *file = (struct cch_backing_file *)backing;
+ aki_mutex_unlock(&file->mutex);
+}
+
static void file_backing_resize(struct cch_backing *backing, size_t size)
{
struct cch_backing_file *file = (struct cch_backing_file *)backing;
@@ -49,6 +64,7 @@ static void file_backing_free(struct cch_backing **backing)
{
struct cch_backing_file *file = (struct cch_backing_file *)*backing;
aki_file_close(&file->file);
+ al_array_free(file->backing.available);
al_free(file);
*backing = NULL;
}
@@ -56,15 +72,16 @@ static void file_backing_free(struct cch_backing **backing)
struct cch_backing *cch_backing_file_create(str *path, size_t size)
{
struct cch_backing_file *file = al_alloc_object(struct cch_backing_file);
- file->backing.mode = CCH_BACKING_READ;
+ file->backing.mode = CACHE_BACKING_READ;
+ file->backing.lock = file_backing_lock;
file->backing.write = file_backing_write;
file->backing.read = file_backing_read;
file->backing.get_ptr = NULL;
- file->backing.unlock = NULL;
- file->backing.get_mmap = NULL;
+ file->backing.unlock = file_backing_unlock;
file->backing.get_size_estimate = file_backing_get_size_estimate;
file->backing.resize = file_backing_resize;
file->backing.free = file_backing_free;
+ al_array_init(file->backing.available);
if (!file_open_internal(file, path, size)) {
al_free(file);
return NULL;
diff --git a/src/cache/backings/file.h b/src/cache/backings/file.h
index 9794bbb..3f8437b 100644
--- a/src/cache/backings/file.h
+++ b/src/cache/backings/file.h
@@ -13,8 +13,8 @@ struct cch_backing_file {
off_t size;
off_t filesize;
union {
- void *map; // CCH_BACKING_MAPPED
- off_t pointer; // CCH_BACKING_READ
+ void *map; // CACHE_BACKING_MAPPED
+ off_t pointer; // CACHE_BACKING_READ
} u;
struct aki_mutex mutex;
};
diff --git a/src/cache/backings/file_common.c b/src/cache/backings/file_common.c
index 232580a..35322fd 100644
--- a/src/cache/backings/file_common.c
+++ b/src/cache/backings/file_common.c
@@ -14,6 +14,7 @@ static bool file_open_internal(struct cch_backing_file *file, str *path, size_t
file->filesize = aki_file_get_filesize(&file->file);
if (!size) {
file->size = file->filesize;
+ cch_backing_fill_range(&file->backing, 0, file->size);
} else {
file->size = -1;
if (file->filesize < (off_t)size) {
diff --git a/src/cache/backings/file_mapped.c b/src/cache/backings/file_mapped.c
index 4277c21..6c6fc94 100644
--- a/src/cache/backings/file_mapped.c
+++ b/src/cache/backings/file_mapped.c
@@ -1,14 +1,23 @@
+#include "../range.h"
+
#include "file.h"
#include "file_common.c"
+static void file_backing_lock(struct cch_backing *backing)
+{
+ struct cch_backing_file *file = (struct cch_backing_file *)backing;
+ aki_mutex_lock(&file->mutex);
+}
+
static void file_backing_write(struct cch_backing *backing, u8 *buf, off_t index, size_t *size)
{
struct cch_backing_file *file = (struct cch_backing_file *)backing;
aki_mutex_lock(&file->mutex);
- if (file->size >= 0 && index + ((off_t)*size) >= file->size) {
- *size = file->size - index;
+ if (file->size >= 0 && index + (off_t)*size >= file->size) {
+ *size = AL_MAX(file->size - index, 0);
}
al_memcpy(file->u.map + index, buf, *size);
+ cch_backing_fill_range(&file->backing, index, *size);
aki_mutex_unlock(&file->mutex);
}
@@ -16,8 +25,8 @@ static u8 *file_backing_get_ptr(struct cch_backing *backing, off_t index, size_t
{
struct cch_backing_file *file = (struct cch_backing_file *)backing;
aki_mutex_lock(&file->mutex);
- if (file->size >= 0 && index + ((off_t)*size) >= file->size) {
- *size = file->size - index;
+ if (file->size >= 0 && index + (off_t)*size >= file->size) {
+ *size = AL_MAX(file->size - index, 0);
}
return (u8 *)(file->u.map + index);
}
@@ -28,24 +37,15 @@ static void file_backing_unlock(struct cch_backing *backing)
aki_mutex_unlock(&file->mutex);
}
-static void *file_backing_get_mmap(struct cch_backing *backing)
-{
- struct cch_backing_file *file = (struct cch_backing_file *)backing;
- aki_mutex_lock(&file->mutex);
- void *map = file->u.map;
- aki_mutex_unlock(&file->mutex);
- return map;
-}
-
static void file_backing_resize(struct cch_backing *backing, size_t size)
{
struct cch_backing_file *file = (struct cch_backing_file *)backing;
aki_mutex_lock(&file->mutex);
file->size = size;
if ((off_t)size > file->filesize) {
- // TODO: Unmap, truncate, map?
- file->u.map = aki_file_mremap(&file->file, size, file->u.map);
+ aki_file_munmap(&file->file, file->u.map);
aki_file_truncate(&file->file, size);
+ file->u.map = aki_file_mmap(&file->file);
file->filesize = size;
}
aki_mutex_unlock(&file->mutex);
@@ -56,6 +56,7 @@ static void file_backing_free(struct cch_backing **backing)
struct cch_backing_file *file = (struct cch_backing_file *)*backing;
aki_file_munmap(&file->file, file->u.map);
aki_file_close(&file->file);
+ al_array_free(file->backing.available);
al_free(file);
*backing = NULL;
}
@@ -63,20 +64,20 @@ static void file_backing_free(struct cch_backing **backing)
struct cch_backing *cch_backing_file_create(str *path, size_t size)
{
struct cch_backing_file *file = al_alloc_object(struct cch_backing_file);
- file->backing.mode = CCH_BACKING_MAPPED;
+ file->backing.mode = CACHE_BACKING_MAPPED;
+ file->backing.lock = file_backing_lock;
file->backing.write = file_backing_write;
file->backing.read = NULL;
- file->backing.get_mmap = file_backing_get_mmap;
file->backing.get_ptr = file_backing_get_ptr;
file->backing.unlock = file_backing_unlock;
file->backing.get_size_estimate = file_backing_get_size_estimate;
file->backing.resize = file_backing_resize;
file->backing.free = file_backing_free;
- if (!file_open_internal(file, path, size)) {
+ al_array_init(file->backing.available);
+ if (!file_open_internal(file, path, size) || !(file->u.map = aki_file_mmap(&file->file))) {
al_free(file);
return NULL;
}
- file->u.map = aki_file_mmap(&file->file);
aki_mutex_init(&file->mutex);
return (struct cch_backing *)file;
}
diff --git a/src/cache/backings/memory.c b/src/cache/backings/memory.c
index 194d66c..1dca8f9 100644
--- a/src/cache/backings/memory.c
+++ b/src/cache/backings/memory.c
@@ -1,22 +1,31 @@
+#include "../range.h"
+
#include "memory.h"
static void ensure_alloced(struct cch_backing_memory *mem, size_t size)
{
if (size > mem->alloc) {
- mem->data = al_realloc(mem->data, size);
- mem->alloc = size;
+ mem->alloc = al_next_power_of_two(size);
+ mem->data = al_realloc(mem->data, mem->alloc);
}
}
+static void memory_backing_lock(struct cch_backing *backing)
+{
+ struct cch_backing_memory *mem = (struct cch_backing_memory *)backing;
+ aki_mutex_lock(&mem->mutex);
+}
+
static void memory_backing_write(struct cch_backing *backing, u8 *buf, off_t index, size_t *size)
{
struct cch_backing_memory *mem = (struct cch_backing_memory *)backing;
aki_mutex_lock(&mem->mutex);
- if (mem->size >= 0 && index + ((off_t)*size) >= mem->size) {
- *size = mem->size - index;
+ if (mem->size >= 0 && index + (off_t)*size >= mem->size) {
+ *size = AL_MAX(mem->size - index, 0);
}
ensure_alloced(mem, index + *size);
al_memcpy(mem->data + index, buf, *size);
+ cch_backing_fill_range(&mem->backing, index, *size);
aki_mutex_unlock(&mem->mutex);
}
@@ -24,8 +33,8 @@ static u8 *memory_backing_get_ptr(struct cch_backing *backing, off_t index, size
{
struct cch_backing_memory *mem = (struct cch_backing_memory *)backing;
aki_mutex_lock(&mem->mutex);
- if (mem->size >= 0 && index + ((off_t)*size) >= mem->size) {
- *size = mem->size - index;
+ if (mem->size >= 0 && index + (off_t)*size >= mem->size) {
+ *size = AL_MAX(mem->size - index, 0);
}
return (u8 *)(mem->data + index);
}
@@ -59,6 +68,7 @@ static void memory_backing_free(struct cch_backing **backing)
struct cch_backing_memory *mem = (struct cch_backing_memory *)*backing;
al_free(mem->data);
aki_mutex_destroy(&mem->mutex);
+ al_array_free(mem->backing.available);
al_free(mem);
*backing = NULL;
}
@@ -66,9 +76,9 @@ static void memory_backing_free(struct cch_backing **backing)
struct cch_backing *cch_backing_memory_create(size_t size)
{
struct cch_backing_memory *mem = al_alloc_object(struct cch_backing_memory);
- mem->backing.mode = CCH_BACKING_MAPPED;
+ mem->backing.mode = CACHE_BACKING_MAPPED;
+ mem->backing.lock = memory_backing_lock;
mem->backing.write = memory_backing_write;
- mem->backing.get_mmap = NULL;
mem->backing.get_ptr = memory_backing_get_ptr;
mem->backing.unlock = memory_backing_unlock;
mem->backing.get_size_estimate = memory_backing_get_size_estimate;
@@ -78,5 +88,6 @@ struct cch_backing *cch_backing_memory_create(size_t size)
mem->alloc = size;
mem->size = -1;
aki_mutex_init(&mem->mutex);
+ al_array_init(mem->backing.available);
return (struct cch_backing *)mem;
}
diff --git a/src/cache/entry.c b/src/cache/entry.c
index 04e8f92..8068397 100644
--- a/src/cache/entry.c
+++ b/src/cache/entry.c
@@ -16,13 +16,26 @@ bool cch_entry_get_handle(struct cch_entry *entry, struct cch_handle *handle)
return true;
}
+str *cch_entry_get_liana(struct cch_entry *entry)
+{
+ return &entry->handler->liana;
+}
+
void cch_entry_set_size(struct cch_entry *entry, off_t filesize)
{
- return entry->backing->resize(entry->backing, filesize);
+ if (filesize == 0) {
+ // The backing will keep size = -1 internally.
+ entry->unknown_size = true;
+ return;
+ }
+ entry->backing->resize(entry->backing, filesize);
}
off_t cch_entry_get_size(struct cch_entry *entry)
{
+ if (entry->unknown_size) {
+ return 0;
+ }
return entry->backing->get_size_estimate(entry->backing);
}
diff --git a/src/cache/entry.h b/src/cache/entry.h
index 305fb1f..3196809 100644
--- a/src/cache/entry.h
+++ b/src/cache/entry.h
@@ -6,15 +6,24 @@
#include "backing.h"
#include "handler.h"
+struct cch_chapter {
+ off_t start;
+ off_t end;
+};
+
struct cch_entry {
u32 hash;
s32 ref_count;
+ bool unknown_size;
+ array(struct cch_chapter) chapters;
+ struct cch_chapter *chapter;
struct cch_backing *backing;
struct cch_handler *handler;
struct aki_mutex mutex;
};
bool cch_entry_get_handle(struct cch_entry *entry, struct cch_handle *handle);
+str *cch_entry_get_liana(struct cch_entry *entry);
void cch_entry_set_size(struct cch_entry *entry, off_t filesize);
off_t cch_entry_get_size(struct cch_entry *entry);
void cch_entry_return_handle(struct cch_entry *entry, struct cch_handle *handle);
diff --git a/src/cache/handle.c b/src/cache/handle.c
index 9dfaf70..5bc868c 100644
--- a/src/cache/handle.c
+++ b/src/cache/handle.c
@@ -6,26 +6,32 @@
#include "handle.h"
#include "entry.h"
+#include "threaded_waits.h"
-static bool wait_for_size(struct cch_handle *handle, struct cch_handler *handler)
+static off_t wait_for_size(struct cch_handle *handle, struct cch_handler *handler)
{
- handle->wait.id = al_rand_u16();
handle->wait.end = -1;
- return handler->wait_for_range(handler, &handle->wait);
+ if (!handler->wait_for_range(handler, &handle->wait)) {
+ return -1;
+ }
+ return cch_entry_get_size(handler->entry);
}
s32 cch_handle_read(struct cch_handle *handle, u8 *buf, s32 size)
{
struct cch_handler *handler = handle->entry->handler;
- wait_for_size(handle, handler);
+ // filesize < 0: Size is yet to be evaluated and we can wait on it.
+ // filesize = 0: Size is explicitly unknown.
off_t filesize = cch_entry_get_size(handle->entry);
- if (filesize >= 0) {
+ if (size < 0) {
+ filesize = wait_for_size(handle, handler);
+ }
+ if (filesize > 0) {
if (handle->pointer >= filesize) return CAMU_ERR_EOF;
if (handle->pointer + size >= filesize) {
size = filesize - handle->pointer;
}
}
- handle->wait.id = al_rand_u16();
handle->wait.start = handle->pointer;
handle->wait.end = handle->pointer + size;
if (!handler->wait_for_range(handler, &handle->wait)) {
@@ -33,25 +39,27 @@ s32 cch_handle_read(struct cch_handle *handle, u8 *buf, s32 size)
}
struct cch_backing *backing = handle->entry->backing;
size_t available = (size_t)size;
- if (backing->mode == CCH_BACKING_MAPPED) {
+ if (backing->mode == CACHE_BACKING_MAPPED) {
u8 *ptr = backing->get_ptr(backing, handle->pointer, &available);
if (ptr && available > 0) {
al_memcpy(buf, ptr, available);
}
backing->unlock(backing);
- } else if (backing->mode == CCH_BACKING_READ) {
+ } else if (backing->mode == CACHE_BACKING_READ) {
backing->read(backing, buf, handle->pointer, &available);
}
handle->pointer += available;
- return (available > 0) ? (s32)available : CAMU_ERR_EOF;
+ return available > 0 ? (s32)available : CAMU_ERR_EOF;
}
off_t cch_handle_seek(struct cch_handle *handle, off_t offset, s32 whence)
{
struct cch_handler *handler = handle->entry->handler;
- wait_for_size(handle, handler);
off_t filesize = cch_entry_get_size(handle->entry);
- if (filesize < 0) return -1;
+ if (filesize < 0) {
+ filesize = wait_for_size(handle, handler);
+ }
+ if (filesize <= 0) return -1;
if (whence == CAMU_SEEK_SIZE) {
al_log_debug("cache_handle", "seek_size");
return filesize;
@@ -91,10 +99,5 @@ bool cch_handle_can_seek(struct cch_handle *handle)
void cch_handle_disable(struct cch_handle *handle)
{
- aki_mutex_lock(&handle->wait.mutex);
- handle->wait.disabled = true;
- if (aki_cond_is_waiting(&handle->wait.cond)) {
- aki_cond_signal(&handle->wait.cond);
- }
- aki_mutex_unlock(&handle->wait.mutex);
+ cch_threaded_wait_disable(&handle->wait);
}
diff --git a/src/cache/handle.h b/src/cache/handle.h
index 55223aa..ae936f3 100644
--- a/src/cache/handle.h
+++ b/src/cache/handle.h
@@ -3,7 +3,7 @@
#include <al/types.h>
#include <aki/thread.h>
-#include "handler.h"
+#include "wait.h"
struct cch_entry;
struct cch_handle {
diff --git a/src/cache/handler.h b/src/cache/handler.h
index 5a2861e..9dd1896 100644
--- a/src/cache/handler.h
+++ b/src/cache/handler.h
@@ -2,15 +2,10 @@
#include <al/types.h>
#include <al/array.h>
+#include <al/str.h>
#include <aki/thread.h>
-struct cch_handler_wait {
- u16 id;
- off_t start, end;
- struct aki_cond cond;
- struct aki_mutex mutex;
- bool disabled;
-};
+#include "handle.h"
struct cch_handler {
bool (*can_seek)(struct cch_handler *);
@@ -18,6 +13,8 @@ struct cch_handler {
bool (*wait_for_range)(struct cch_handler *, struct cch_handler_wait *);
void (*free)(struct cch_handler **);
struct cch_entry *entry;
- struct cch_backing *backing;
+ str liana;
+ bool disabled;
+ struct aki_mutex mutex;
array(struct cch_handler_wait *) waits;
};
diff --git a/src/cache/handlers/cdio.c b/src/cache/handlers/cdio.c
new file mode 100644
index 0000000..23f87b6
--- /dev/null
+++ b/src/cache/handlers/cdio.c
@@ -0,0 +1,206 @@
+#include <al/log.h>
+
+#include "../backings/file.h"
+#include "../backings/memory.h"
+#include "../threaded_waits.h"
+#include "../range.h"
+
+#include "cdio.h"
+
+// 1 sector at a time to minimize skips
+#define SECTORS_PER_STEP 1
+#define BYTES_PER_STEP (SECTORS_PER_STEP * CDIO_CD_FRAMESIZE_RAW)
+
+static void cdio_log_messages(struct cch_handler_cdio *cdio)
+{
+ char *error_messages = cdio_cddap_errors(cdio->drive);
+ char *messages = cdio_cddap_messages(cdio->drive);
+ if (error_messages) {
+ al_log_error("cdio", "\n%s", error_messages);
+ cdio_cddap_free_messages(error_messages);
+ }
+ if (messages) {
+ al_log_info("cdio", "\n%s", messages);
+ cdio_cddap_free_messages(messages);
+ }
+}
+
+static bool handler_cdio_can_seek(struct cch_handler *handler)
+{
+ (void)handler;
+ return true;
+}
+
+static aki_thread_result AKI_THREADCALL cd_read_thread(void *userdata)
+{
+ struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)userdata;
+ while (1) {
+ if (!al_atomic_load(s32)(&cdio->running, AL_ATOMIC_RELAXED)) {
+ // We don't care about how complete the disc read was.
+ return 0;
+ }
+ // cdio_read() here can block for a very long time so we buffer each read
+ // before writing it to the backing.
+ long ret = cdio_cddap_read(cdio->drive, aki_buffer_get_ptr(&cdio->buffer, 0), cdio->sector, SECTORS_PER_STEP);
+ cdio_log_messages(cdio);
+ al_assert(ret <= SECTORS_PER_STEP);
+ off_t pointer = cdio->sector * CDIO_CD_FRAMESIZE_RAW;
+ size_t size;
+ u8 *ptr = cdio->backing->get_ptr(cdio->backing, pointer, &size);
+ if (size < BYTES_PER_STEP) {
+ al_log_error("cdio", "Tried to write over expected size during normal operation.");
+ cdio->backing->unlock(cdio->backing);
+ break;
+ }
+ if (ret == SECTORS_PER_STEP) {
+ al_memcpy(ptr, aki_buffer_get_ptr(&cdio->buffer, 0), BYTES_PER_STEP);
+ } else if (ret == -10) {
+ al_log_error("cdio", "I/O error, skipping sector.");
+ al_memset(ptr, 0, BYTES_PER_STEP);
+ ret = SECTORS_PER_STEP;
+ } else if (ret < 0) {
+ al_log_error("cdio", "Fatal error (%d).", ret);
+ cdio->backing->unlock(cdio->backing);
+ break;
+ }
+ cch_backing_fill_range(cdio->backing, pointer, BYTES_PER_STEP);
+ cdio->sector += ret;
+ cdio->backing->unlock(cdio->backing);
+ cch_threaded_waits_evaluate(&cdio->handler, cdio->backing);
+ if (ret < SECTORS_PER_STEP || cdio->sector > cdio->end) {
+ al_log_debug("cdio", "Normal CD EOF.");
+ break;
+ }
+ }
+ if (cdio->sector <= cdio->end) {
+ al_log_warn("cdio", "Disc read cut short (sectors: %zd, end: %zd).", cdio->sector, cdio->end);
+ // We didn't read the full disc, disable waiters.
+ cch_threaded_waits_disable_all(&cdio->handler);
+ }
+ return 0;
+}
+
+static void handler_cdio_maybe_spawn_worker(struct cch_handler *handler, size_t index)
+{
+ struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)handler;
+ if (cdio->start == (lsn_t)index) return;
+ if (al_atomic_load(s32)(&cdio->running, AL_ATOMIC_ACQUIRE)) {
+ al_atomic_store(s32)(&cdio->running, 0, AL_ATOMIC_RELEASE);
+ aki_thread_join(&cdio->thread);
+ }
+ cdio->start = index;
+ cdio->sector = cdio->start;
+ al_atomic_store(s32)(&cdio->running, 1, AL_ATOMIC_RELEASE);
+ aki_thread_create(&cdio->thread, cd_read_thread, cdio);
+}
+
+static bool handler_cdio_wait_for_range(struct cch_handler *handler, struct cch_handler_wait *wait)
+{
+ struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)handler;
+ return cch_threaded_wait_for_range(&cdio->handler, cdio->backing, wait);
+}
+
+static void handler_cdio_free(struct cch_handler **handler)
+{
+ struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)*handler;
+ cch_threaded_waits_disable_all(&cdio->handler);
+ if (al_atomic_load(s32)(&cdio->running, AL_ATOMIC_RELAXED)) {
+ al_atomic_store(s32)(&cdio->running, 0, AL_ATOMIC_RELAXED);
+ aki_thread_join(&cdio->thread);
+ }
+ if (cdio->drive) {
+ cdio_log_messages(cdio);
+ cdio_cddap_close(cdio->drive);
+ }
+ cch_threaded_waits_close(&cdio->handler);
+ al_str_free(&cdio->handler.liana);
+ al_free(cdio);
+ *handler = NULL;
+}
+
+static bool open_cd_drive(struct cch_handler_cdio *cdio)
+{
+ cdrom_drive_t *drive = NULL;
+ char **cd_drives;
+ CdIo_t *cdio_handle = NULL;
+
+ cd_drives = cdio_get_devices_with_cap(NULL, CDIO_FS_AUDIO, false);
+ if (cd_drives) {
+ // Use first drive.
+ cdio_handle = cdio_open(*cd_drives, DRIVER_UNKNOWN);
+ drive = cdio_cddap_identify_cdio(cdio_handle, CDDA_MESSAGE_LOGIT, NULL);
+ } else {
+ al_log_error("cdio", "No compatible disc found.");
+ return false;
+ }
+ cdio_free_device_list(cd_drives);
+
+ cdio_cddap_verbose_set(drive, CDDA_MESSAGE_LOGIT, CDDA_MESSAGE_LOGIT);
+
+ s32 ret = cdio_cddap_open(drive);
+ if (ret != 0) {
+ al_log_error("cdio", "Unable to open disc (%d).", ret);
+ return false;
+ }
+
+ ret = cdio_cddap_speed_set(drive, 4);
+ if (ret != 0) {
+ al_log_warn("cdio", "Failed to set drive speed (%d).", ret);
+ }
+
+ // Not sure what this is based on, but it can seemingly just be wrong.
+ track_t tracks = cdio_cddap_tracks(drive);
+ al_log_info("cdio", "CD with %d tracks loaded.", tracks);
+ al_array_reserve(cdio->handler.entry->chapters, tracks);
+
+ lsn_t start = cdio_cddap_disc_firstsector(drive);
+ lsn_t end = cdio_cddap_disc_lastsector(drive);
+ lsn_t offset = start;
+ s32 track = CDIO_INVALID_TRACK;
+ while (offset < end) {
+ track = cdio_cddap_sector_gettrack(drive, offset);
+ al_assert(track != CDIO_INVALID_TRACK);
+ lsn_t track_start = cdio_cddap_track_firstsector(drive, track);
+ lsn_t track_end = cdio_cddap_track_lastsector(drive, track);
+ al_array_push(cdio->handler.entry->chapters, ((struct cch_chapter){ track_start, track_end }));
+ offset = track_end + 1;
+ }
+ // end is a valid index so add one framesize.
+ cch_entry_set_size(cdio->handler.entry, ((end - start) + 1) * CDIO_CD_FRAMESIZE_RAW);
+ cdio->start = -1;
+ cdio->end = end;
+
+ cdio->drive = drive;
+
+ cdio_log_messages(cdio);
+
+ return true;
+}
+
+struct cch_entry *cch_handler_cdio_create(void)
+{
+ struct cch_backing *backing = cch_backing_file_create(al_str_c("/tmp/camu_cd_data"), 4096);
+ if (!backing) return NULL;
+ struct cch_handler_cdio *cdio = al_alloc_object(struct cch_handler_cdio);
+ cdio->backing = backing;
+ struct cch_entry *entry = al_alloc_object(struct cch_entry);
+ entry->backing = backing;
+ entry->handler = (struct cch_handler *)cdio;
+ entry->ref_count = 0;
+ entry->unknown_size = false;
+ entry->hash = 0;
+ aki_mutex_init(&entry->mutex);
+ entry->handler->can_seek = handler_cdio_can_seek;
+ entry->handler->maybe_spawn_worker = handler_cdio_maybe_spawn_worker;
+ entry->handler->wait_for_range = handler_cdio_wait_for_range;
+ entry->handler->free = handler_cdio_free;
+ entry->handler->entry = entry;
+ al_str_from(&entry->handler->liana, "cdio");
+ if (!open_cd_drive(cdio)) return NULL;
+ al_atomic_store(s32)(&cdio->running, 0, AL_ATOMIC_RELAXED);
+ aki_buffer_init(&cdio->buffer);
+ aki_buffer_ensure_space(&cdio->buffer, BYTES_PER_STEP);
+ aki_buffer_set_size(&cdio->buffer, BYTES_PER_STEP);
+ cch_threaded_waits_init(&cdio->handler);
+ return entry;
+}
diff --git a/src/cache/handlers/cdio.h b/src/cache/handlers/cdio.h
new file mode 100644
index 0000000..953c988
--- /dev/null
+++ b/src/cache/handlers/cdio.h
@@ -0,0 +1,21 @@
+#include <al/atomic.h>
+#include <aki/common.h>
+#include <cdio/paranoia/cdda.h>
+#include <cdio/cd_types.h>
+
+#include "../handler.h"
+#include "../entry.h"
+
+struct cch_handler_cdio {
+ struct cch_handler handler;
+ struct cch_backing *backing;
+ cdrom_drive_t *drive;
+ lsn_t start;
+ lsn_t end;
+ lsn_t sector;
+ atomic(s32) running;
+ struct aki_thread thread;
+ struct aki_buffer buffer;
+};
+
+struct cch_entry *cch_handler_cdio_create(void);
diff --git a/src/cache/handlers/file.c b/src/cache/handlers/file.c
index ec6a146..03e2814 100644
--- a/src/cache/handlers/file.c
+++ b/src/cache/handlers/file.c
@@ -25,6 +25,7 @@ static bool handler_file_wait_for_range(struct cch_handler *handler, struct cch_
static void handler_file_free(struct cch_handler **handler)
{
struct cch_handler_file *file = (struct cch_handler_file *)*handler;
+ al_str_free(&file->handler.liana);
al_free(file);
*handler = NULL;
}
@@ -33,17 +34,20 @@ struct cch_entry *cch_handler_file_create(str *path)
{
struct cch_backing *backing = cch_backing_file_create(path, 0);
if (!backing) return NULL;
+ struct cch_handler_file *file = al_alloc_object(struct cch_handler_file);
+ file->backing = backing;
struct cch_entry *entry = al_alloc_object(struct cch_entry);
entry->backing = backing;
+ entry->handler = (struct cch_handler *)file;
entry->ref_count = 0;
+ entry->unknown_size = false;
entry->hash = 0;
- entry->handler = (struct cch_handler *)al_alloc_object(struct cch_handler_file);
aki_mutex_init(&entry->mutex);
entry->handler->can_seek = handler_file_can_seek;
entry->handler->maybe_spawn_worker = handler_file_maybe_spawn_worker;
entry->handler->wait_for_range = handler_file_wait_for_range;
entry->handler->free = handler_file_free;
entry->handler->entry = entry;
- entry->handler->backing = entry->backing;
+ al_str_from(&entry->handler->liana, "codec");
return entry;
}
diff --git a/src/cache/handlers/file.h b/src/cache/handlers/file.h
index 52e5c94..85e1c7f 100644
--- a/src/cache/handlers/file.h
+++ b/src/cache/handlers/file.h
@@ -7,6 +7,7 @@
struct cch_handler_file {
struct cch_handler handler;
+ struct cch_backing *backing;
};
struct cch_entry *cch_handler_file_create(str *path);
diff --git a/src/cache/handlers/http.c b/src/cache/handlers/http.c
index 789b247..71ae186 100644
--- a/src/cache/handlers/http.c
+++ b/src/cache/handlers/http.c
@@ -1,8 +1,10 @@
#include <al/random.h>
#include <al/log.h>
-#include "../backings/file.h"
#include "../backings/memory.h"
+#include "../backings/file.h"
+#include "../threaded_waits.h"
+#include "../wait.h"
#include "http.h"
@@ -14,21 +16,6 @@ static bool handler_http_can_seek(struct cch_handler *handler)
return false;
}
-static void disable_waiters_internal(struct cch_handler_http *http)
-{
- aki_mutex_lock(&http->mutex);
- // Don't allow any further waits.
- http->disabled = true;
- struct cch_handler_wait *wait;
- al_array_foreach(http->handler.waits, i, wait) {
- aki_mutex_lock(&wait->mutex);
- wait->disabled = true;
- aki_cond_signal(&wait->cond);
- aki_mutex_unlock(&wait->mutex);
- }
- aki_mutex_unlock(&http->mutex);
-}
-
static size_t http_callback(void *userdata, u8 op, u8 *buf, s64 int0)
{
struct cch_handler_http *http = (struct cch_handler_http *)userdata;
@@ -39,8 +26,7 @@ static size_t http_callback(void *userdata, u8 op, u8 *buf, s64 int0)
break;
}
case AKI_HTTP_ERROR: {
- al_log_debug("cache_handler_http", "Error.");
- disable_waiters_internal(http);
+ cch_threaded_waits_disable_all(&http->handler);
break;
}
case AKI_HTTP_RESPONSE_CODE: {
@@ -54,37 +40,18 @@ static size_t http_callback(void *userdata, u8 op, u8 *buf, s64 int0)
case AKI_HTTP_CONTENT_LENGTH: {
off_t content_length = (off_t)int0;
al_log_debug("cache_handler_http", "Content-Length: %lld.", content_length);
- aki_mutex_lock(&http->mutex);
cch_entry_set_size(http->handler.entry, content_length);
- struct cch_handler_wait *wait;
- al_array_foreach(http->handler.waits, i, wait) {
- if (wait->end < 0) {
- aki_mutex_lock(&wait->mutex);
- aki_cond_signal(&wait->cond);
- al_array_remove_at_iter(http->handler.waits, i);
- aki_mutex_unlock(&wait->mutex);
- }
- }
- aki_mutex_unlock(&http->mutex);
+ cch_threaded_waits_signal_any(&http->handler);
break;
}
case AKI_HTTP_WRITE: {
- http->handler.backing->write(http->handler.backing, buf, http->pointer, &ret);
- aki_mutex_lock(&http->mutex);
- http->pointer += ret;
- struct cch_handler_wait *wait;
- al_array_foreach(http->handler.waits, i, wait) {
- aki_mutex_lock(&wait->mutex);
- // Assume that if we get data before a length, we won't get a length.
- // So, signal the waiter (wait_for_range returns false).
- if (wait->end < 0) wait->disabled = true;
- if (wait->disabled || http->pointer >= wait->end) {
- aki_cond_signal(&wait->cond);
- al_array_remove_at_iter(http->handler.waits, i);
- }
- aki_mutex_unlock(&wait->mutex);
+ // If we got data before a Content-Length, assume the size is unknown.
+ if (cch_entry_get_size(http->handler.entry) < 0) {
+ cch_entry_set_size(http->handler.entry, 0);
}
- aki_mutex_unlock(&http->mutex);
+ http->backing->write(http->backing, buf, http->pointer, &ret);
+ http->pointer += ret;
+ cch_threaded_waits_evaluate(&http->handler, http->backing);
break;
}
default:
@@ -93,123 +60,63 @@ static size_t http_callback(void *userdata, u8 op, u8 *buf, s64 int0)
return ret;
}
-static void signal_callback(void *userdata)
-{
- struct cch_handler_http *http = (struct cch_handler_http *)userdata;
- u32 size;
- s32 index;
- do {
- camu_queue_try_pop(http->queue, size, index);
- if (size == 0) break;
- if (index == -1) {
- aki_event_loop_break(&http->loop);
- break;
- }
- al_array_push(http->requests, (struct aki_http){0});
- struct aki_http *request = &al_array_last(http->requests);
- aki_http_init(request);
- aki_http_set_url(request, &http->url);
- aki_http_set_user_agent(request, USER_AGENT);
- aki_http_request_stream(request, AKI_HTTP_GET, &http->loop, http_callback, http);
- } while (1);
-}
-
static void handler_http_maybe_spawn_worker(struct cch_handler *handler, size_t index)
{
struct cch_handler_http *http = (struct cch_handler_http *)handler;
- camu_queue_push(http->queue, index);
- aki_signal_send(&http->signal);
+ (void)index;
+ al_array_push(http->requests, (struct aki_http){ 0 });
+ struct aki_http *request = &al_array_last(http->requests);
+ aki_http_init(request);
+ aki_http_set_url(request, &http->url);
+ aki_http_set_user_agent(request, USER_AGENT);
+ aki_http_request_stream(request, AKI_HTTP_GET, http->loop, http_callback, http);
}
static bool handler_http_wait_for_range(struct cch_handler *handler, struct cch_handler_wait *wait)
{
struct cch_handler_http *http = (struct cch_handler_http *)handler;
- aki_mutex_lock(&http->mutex);
- bool canceled = http->disabled;
- if (!canceled && (cch_entry_get_size(http->handler.entry) < 0 || http->pointer < wait->end)) {
- aki_mutex_lock(&wait->mutex);
- if (!wait->disabled) {
- al_array_push(http->handler.waits, wait);
- aki_mutex_unlock(&http->mutex);
- aki_cond_wait(&wait->cond, &wait->mutex);
- } else {
- aki_mutex_unlock(&wait->mutex);
- aki_mutex_unlock(&http->mutex);
- return false;
- }
- aki_mutex_unlock(&wait->mutex);
- aki_mutex_lock(&http->mutex);
- if (wait->disabled) {
- u16 id = wait->id;
- al_array_foreach(http->handler.waits, i, wait) {
- if (wait->id == id) {
- al_array_remove_at_iter(http->handler.waits, i);
- break;
- }
- }
- canceled = true;
- }
- }
- aki_mutex_unlock(&http->mutex);
- return !canceled;
+ return cch_threaded_wait_for_range(&http->handler, http->backing, wait);
}
static void handler_http_free(struct cch_handler **handler)
{
struct cch_handler_http *http = (struct cch_handler_http *)*handler;
- disable_waiters_internal(http);
- camu_queue_push(http->queue, -1);
- aki_signal_send(&http->signal);
- aki_thread_join(&http->thread);
- aki_mutex_destroy(&http->mutex);
+ cch_threaded_waits_disable_all(&http->handler);
struct aki_http *request;
al_array_foreach_ptr(http->requests, i, request) {
aki_http_close(request);
}
+ cch_threaded_waits_close(&http->handler);
al_array_free(http->requests);
- camu_queue_free(http->queue);
al_str_free(&http->url);
- al_array_free(http->handler.waits);
+ al_str_free(&http->handler.liana);
al_free(http);
*handler = NULL;
}
-static aki_thread_result AKI_THREADCALL event_loop_thread(void *userdata)
-{
- struct cch_handler_http *http = (struct cch_handler_http *)userdata;
- aki_event_loop_run(&http->loop);
- aki_event_loop_destroy(&http->loop);
- return 0;
-}
-
-struct cch_entry *cch_handler_http_create(str *url)
+struct cch_entry *cch_handler_http_create(str *url, struct aki_event_loop *loop)
{
- //struct cch_backing *backing = cch_backing_file_create(al_str_c("/tmp/camu_http_data"), 1024 * 128);
struct cch_backing *backing = cch_backing_memory_create(1024 * 128);
if (!backing) return NULL;
+ struct cch_handler_http *http = al_alloc_object(struct cch_handler_http);
+ http->backing = backing;
struct cch_entry *entry = al_alloc_object(struct cch_entry);
entry->backing = backing;
+ entry->handler = (struct cch_handler *)http;
entry->ref_count = 0;
+ entry->unknown_size = false;
entry->hash = 0;
- entry->handler = (struct cch_handler *)al_alloc_object(struct cch_handler_http);
aki_mutex_init(&entry->mutex);
entry->handler->can_seek = handler_http_can_seek;
entry->handler->maybe_spawn_worker = handler_http_maybe_spawn_worker;
entry->handler->wait_for_range = handler_http_wait_for_range;
entry->handler->free = handler_http_free;
entry->handler->entry = entry;
- entry->handler->backing = entry->backing;
- al_array_init(entry->handler->waits);
- struct cch_handler_http *http = (struct cch_handler_http *)entry->handler;
+ al_str_from(&entry->handler->liana, "codec");
+ http->loop = loop;
al_str_clone(&http->url, url);
http->pointer = 0;
- http->disabled = false;
al_array_init(http->requests);
- camu_queue_init(http->queue);
- aki_event_loop_init(&http->loop);
- aki_signal_init(&http->signal, signal_callback, http);
- aki_signal_start(&http->signal, &http->loop);
- aki_mutex_init(&http->mutex);
- aki_thread_create(&http->thread, event_loop_thread, http);
+ cch_threaded_waits_init(&http->handler);
return entry;
}
diff --git a/src/cache/handlers/http.h b/src/cache/handlers/http.h
index e8fb2e0..5f9df86 100644
--- a/src/cache/handlers/http.h
+++ b/src/cache/handlers/http.h
@@ -4,22 +4,16 @@
#include <aki/thread.h>
#include <aki/signal.h>
-#include "../../util/queue.h"
-
#include "../handler.h"
#include "../entry.h"
struct cch_handler_http {
struct cch_handler handler;
- off_t pointer;
+ struct cch_backing *backing;
str url;
- bool disabled;
+ off_t pointer;
array(struct aki_http) requests;
- queue(s32) queue;
- struct aki_event_loop loop;
- struct aki_signal signal;
- struct aki_mutex mutex;
- struct aki_thread thread;
+ struct aki_event_loop *loop;
};
-struct cch_entry *cch_handler_http_create(str *url);
+struct cch_entry *cch_handler_http_create(str *url, struct aki_event_loop *loop);
diff --git a/src/cache/meson.build b/src/cache/meson.build
index 099b2a6..8684ccd 100644
--- a/src/cache/meson.build
+++ b/src/cache/meson.build
@@ -1,7 +1,9 @@
cache_src = [
'entry.c',
'handle.c',
+ 'threaded_waits.c',
'handlers/file.c',
+ 'handlers/cdio.c',
'backings/memory.c'
]
diff --git a/src/cache/range.h b/src/cache/range.h
new file mode 100644
index 0000000..598b655
--- /dev/null
+++ b/src/cache/range.h
@@ -0,0 +1,23 @@
+#include <al/types.h>
+
+#include "backing.h"
+
+AL_UNUSED_FUNCTION_PUSH
+
+// TODO: cch_backing_remove_range.
+//
+// TODO: report overlap, don't unlock in backing_write() and handle fill in handler
+
+static void cch_backing_fill_range(struct cch_backing *backing, off_t index, off_t size)
+{
+ struct cch_range *range;
+ al_array_foreach_ptr(backing->available, i, range) {
+ if (index >= range->start && index <= range->end) {
+ range->end = AL_MAX(index + size, range->end);
+ return;
+ }
+ }
+ al_array_push(backing->available, ((struct cch_range){ index, index + size }));
+}
+
+AL_UNUSED_FUNCTION_POP
diff --git a/src/cache/threaded_waits.c b/src/cache/threaded_waits.c
new file mode 100644
index 0000000..a93fe85
--- /dev/null
+++ b/src/cache/threaded_waits.c
@@ -0,0 +1,126 @@
+#include "threaded_waits.h"
+#include "entry.h"
+
+void cch_threaded_waits_init(struct cch_handler *handler)
+{
+ handler->disabled = false;
+ aki_mutex_init(&handler->mutex);
+ al_array_init(handler->waits);
+}
+
+static bool wait_range_satisfied(struct cch_backing *backing, struct cch_handler_wait *wait)
+{
+ if (wait->end < 0) return true;
+ struct cch_range *range;
+ al_array_foreach_ptr(backing->available, i, range) {
+ if (wait->start >= range->start && range->end >= wait->end) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool cch_threaded_wait_for_range(struct cch_handler *handler, struct cch_backing *backing,
+ struct cch_handler_wait *wait)
+{
+ aki_mutex_lock(&handler->mutex);
+ bool canceled = handler->disabled;
+ off_t size = cch_entry_get_size(handler->entry);
+ backing->lock(backing);
+ bool satisfied = size >= 0 && wait_range_satisfied(backing, wait);
+ backing->unlock(backing);
+ if (!canceled && !satisfied) {
+ aki_mutex_lock(&wait->mutex);
+ if (!wait->disabled) {
+ al_array_push(handler->waits, wait);
+ aki_mutex_unlock(&handler->mutex);
+ aki_cond_wait(&wait->cond, &wait->mutex);
+ } else {
+ aki_mutex_unlock(&wait->mutex);
+ aki_mutex_unlock(&handler->mutex);
+ return false;
+ }
+ aki_mutex_unlock(&wait->mutex);
+ aki_mutex_lock(&handler->mutex);
+ if (wait->disabled) {
+ struct cch_handler_wait *rwait;
+ al_array_foreach(handler->waits, i, rwait) {
+ if (rwait == wait) {
+ al_array_remove_at(handler->waits, i);
+ break;
+ }
+ }
+ canceled = true;
+ }
+ }
+ aki_mutex_unlock(&handler->mutex);
+ return !canceled;
+}
+
+void cch_threaded_waits_signal_any(struct cch_handler *handler)
+{
+ aki_mutex_lock(&handler->mutex);
+ struct cch_handler_wait *wait;
+ al_array_foreach_rev(handler->waits, i, wait) {
+ if (wait->end < 0) {
+ al_array_remove_at(handler->waits, i);
+ aki_mutex_lock(&wait->mutex);
+ aki_cond_signal(&wait->cond);
+ aki_mutex_unlock(&wait->mutex);
+ }
+ }
+ aki_mutex_unlock(&handler->mutex);
+}
+
+void cch_threaded_wait_disable(struct cch_handler_wait *wait)
+{
+ aki_mutex_lock(&wait->mutex);
+ wait->disabled = true;
+ // cond_is_waiting() could be false if the handler is disabled.
+ if (aki_cond_is_waiting(&wait->cond)) {
+ aki_cond_signal(&wait->cond);
+ }
+ aki_mutex_unlock(&wait->mutex);
+}
+
+void cch_threaded_waits_disable_all(struct cch_handler *handler)
+{
+ aki_mutex_lock(&handler->mutex);
+ if (handler->disabled) {
+ aki_mutex_unlock(&handler->mutex);
+ return;
+ }
+ // Disallow any further waits.
+ handler->disabled = true;
+ struct cch_handler_wait *wait;
+ al_array_foreach(handler->waits, i, wait) {
+ aki_mutex_lock(&wait->mutex);
+ wait->disabled = true;
+ aki_cond_signal(&wait->cond);
+ aki_mutex_unlock(&wait->mutex);
+ }
+ aki_mutex_unlock(&handler->mutex);
+}
+
+void cch_threaded_waits_evaluate(struct cch_handler *handler, struct cch_backing *backing)
+{
+ aki_mutex_lock(&handler->mutex);
+ backing->lock(backing);
+ struct cch_handler_wait *wait;
+ al_array_foreach_rev(handler->waits, i, wait) {
+ aki_mutex_lock(&wait->mutex);
+ if (wait_range_satisfied(backing, wait)) {
+ aki_cond_signal(&wait->cond);
+ al_array_remove_at(handler->waits, i);
+ }
+ aki_mutex_unlock(&wait->mutex);
+ }
+ backing->unlock(backing);
+ aki_mutex_unlock(&handler->mutex);
+}
+
+void cch_threaded_waits_close(struct cch_handler *handler)
+{
+ aki_mutex_destroy(&handler->mutex);
+ al_array_free(handler->waits);
+}
diff --git a/src/cache/threaded_waits.h b/src/cache/threaded_waits.h
new file mode 100644
index 0000000..edbee03
--- /dev/null
+++ b/src/cache/threaded_waits.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <al/types.h>
+
+#include "handler.h"
+#include "backing.h"
+
+void cch_threaded_waits_init(struct cch_handler *handler);
+bool cch_threaded_wait_for_range(struct cch_handler *handler, struct cch_backing *backing,
+ struct cch_handler_wait *wait);
+void cch_threaded_waits_signal_any(struct cch_handler *handler);
+void cch_threaded_wait_disable(struct cch_handler_wait *wait);
+void cch_threaded_waits_disable_all(struct cch_handler *handler);
+void cch_threaded_waits_evaluate(struct cch_handler *handler, struct cch_backing *backing);
+void cch_threaded_waits_close(struct cch_handler *handler);
diff --git a/src/cache/wait.h b/src/cache/wait.h
new file mode 100644
index 0000000..8654362
--- /dev/null
+++ b/src/cache/wait.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <aki/thread.h>
+
+struct cch_handler_wait {
+ off_t start, end;
+ struct aki_cond cond;
+ struct aki_mutex mutex;
+ bool disabled;
+};