Skip to content

Commit bffe0f4

Browse files
committed
Use a separate, re-useable ring-buffer implementation
Not only is it re-useable it also allows for easier debugging by separating the ring buffer specific logic and the pager history logic. Hopefully it fixes #3049
1 parent 3b8be26 commit bffe0f4

File tree

5 files changed

+675
-69
lines changed

5 files changed

+675
-69
lines changed

docs/changelog.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
5252

5353
- Add a ``right`` option for :opt:`tab_switch_strategy` (:pull:`3155`)
5454

55+
- Fix a regression in 0.19.0 that caused a rare crash when using the optional
56+
:opt:`scrollback_pager_history_size` (:iss:`3049`)
57+
5558

5659
0.19.2 [2020-11-13]
5760
-------------------

kitty/data-types.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,8 @@ typedef struct {
193193
} HistoryBufSegment;
194194

195195
typedef struct {
196-
uint8_t *buffer;
197-
size_t buffer_size, max_sz;
198-
size_t start, length;
196+
void *ringbuf;
197+
size_t maximum_size;
199198
bool rewrap_needed;
200199
} PagerHistoryBuf;
201200

kitty/history.c

Lines changed: 43 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "lineops.h"
1010
#include "charsets.h"
1111
#include <structmember.h>
12+
#include "ringbuf.h"
1213

1314
extern PyTypeObject Line_Type;
1415
#define SEGMENT_SIZE 2048
@@ -61,42 +62,37 @@ alloc_pagerhist(size_t pagerhist_sz) {
6162
if (!pagerhist_sz) return NULL;
6263
ph = PyMem_Calloc(1, sizeof(PagerHistoryBuf));
6364
if (!ph) return NULL;
64-
ph->max_sz = pagerhist_sz;
65-
ph->buffer_size = MIN(1024u*1024u, ph->max_sz);
66-
ph->buffer = PyMem_Malloc(ph->buffer_size);
67-
if (!ph->buffer) { PyMem_Free(ph); return NULL; }
65+
size_t sz = MIN(1024u * 1024u, pagerhist_sz);
66+
ph->ringbuf = ringbuf_new(sz);
67+
if (!ph->ringbuf) { PyMem_Free(ph); return NULL; }
68+
ph->maximum_size = pagerhist_sz;
6869
return ph;
6970
}
7071

7172
static inline void
7273
free_pagerhist(HistoryBuf *self) {
73-
if (self->pagerhist) PyMem_Free(self->pagerhist->buffer);
74+
if (self->pagerhist && self->pagerhist->ringbuf) ringbuf_free((ringbuf_t*)&self->pagerhist->ringbuf);
7475
PyMem_Free(self->pagerhist);
7576
self->pagerhist = NULL;
7677
}
7778

7879
static inline bool
7980
pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
80-
if (ph->buffer_size >= ph->max_sz) return false;
81-
size_t newsz = MIN(ph->max_sz, ph->buffer_size + MAX(1024u * 1024u, minsz));
82-
uint8_t *newbuf = PyMem_Malloc(newsz);
81+
size_t buffer_size = ringbuf_capacity(ph->ringbuf);
82+
if (buffer_size >= ph->maximum_size) return false;
83+
size_t newsz = MIN(ph->maximum_size, buffer_size + MAX(1024u * 1024u, minsz));
84+
ringbuf_t newbuf = ringbuf_new(newsz);
8385
if (!newbuf) return false;
84-
size_t copied = MIN(ph->length, ph->buffer_size - ph->start);
85-
if (copied) memcpy(newbuf, ph->buffer + ph->start, copied);
86-
if (copied < ph->length) memcpy(newbuf + copied, ph->buffer, (ph->length - copied));
87-
PyMem_Free(ph->buffer);
88-
ph->start = 0;
89-
ph->buffer = newbuf;
90-
ph->buffer_size = newsz;
86+
size_t count = ringbuf_bytes_used(ph->ringbuf);
87+
if (count) ringbuf_copy(newbuf, ph->ringbuf, count);
88+
ringbuf_free((ringbuf_t*)&ph->ringbuf);
89+
ph->ringbuf = newbuf;
9190
return true;
9291
}
9392

9493
static inline void
9594
pagerhist_clear(HistoryBuf *self) {
96-
if (!self->pagerhist || !self->pagerhist->max_sz) return;
97-
index_type pagerhist_sz = self->pagerhist->max_sz;
98-
free_pagerhist(self);
99-
self->pagerhist = alloc_pagerhist(pagerhist_sz);
95+
if (self->pagerhist && self->pagerhist->ringbuf) ringbuf_reset(self->pagerhist->ringbuf);
10096
}
10197

10298
static HistoryBuf*
@@ -188,36 +184,28 @@ historybuf_clear(HistoryBuf *self) {
188184

189185
static inline bool
190186
pagerhist_write_bytes(PagerHistoryBuf *ph, const uint8_t *buf, size_t sz) {
191-
if (sz > ph->max_sz) return false;
187+
if (sz > ph->maximum_size) return false;
192188
if (!sz) return true;
193-
if (sz > ph->buffer_size - ph->length) pagerhist_extend(ph, sz);
194-
if (sz > ph->buffer_size) return false;
195-
size_t start_writing_at = (ph->start + ph->length) % ph->buffer_size;
196-
size_t available_space = ph->buffer_size - ph->length;
197-
size_t overlap = available_space < sz ? sz - available_space : 0;
198-
size_t copied = MIN(sz, ph->buffer_size - start_writing_at);
199-
ph->length += sz - overlap;
200-
ph->start = (ph->start + overlap) % ph->buffer_size;
201-
if (copied) memcpy(ph->buffer + start_writing_at, buf, copied);
202-
if (copied < sz) memcpy(ph->buffer, buf + copied, (sz - copied));
189+
size_t space_in_ringbuf = ringbuf_bytes_free(ph->ringbuf);
190+
if (sz > space_in_ringbuf) pagerhist_extend(ph, sz);
191+
ringbuf_memcpy_into(ph->ringbuf, buf, sz);
203192
return true;
204193
}
205194

206195
static inline bool
207196
pagerhist_ensure_start_is_valid_utf8(PagerHistoryBuf *ph) {
197+
uint8_t scratch[8];
198+
size_t num = ringbuf_memcpy_from(scratch, ph->ringbuf, arraysz(scratch));
208199
uint32_t state = UTF8_ACCEPT, codep;
209-
size_t pos = ph->start, count = 0;
200+
size_t count = 0;
210201
size_t last_reject_at = 0;
211-
while (count < ph->length) {
212-
decode_utf8(&state, &codep, ph->buffer[pos]);
213-
count++;
202+
while (count < num) {
203+
decode_utf8(&state, &codep, scratch[count++]);
214204
if (state == UTF8_ACCEPT) break;
215205
if (state == UTF8_REJECT) { state = UTF8_ACCEPT; last_reject_at = count; }
216-
pos = pos == ph->buffer_size - 1 ? 0: pos + 1;
217206
}
218207
if (last_reject_at) {
219-
ph->start = (ph->start + last_reject_at) % ph->buffer_size;
220-
ph->length -= last_reject_at;
208+
ringbuf_memmove_from(scratch, ph->ringbuf, last_reject_at);
221209
return true;
222210
}
223211
return false;
@@ -241,7 +229,7 @@ pagerhist_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) {
241229
Line l = {.xnum=self->xnum};
242230
init_line(self, self->start_of_data, &l);
243231
line_as_ansi(&l, as_ansi_buf, &prev_cell);
244-
if (ph->length != 0 && !l.continued) pagerhist_write_bytes(ph, (const uint8_t*)"\n", 1);
232+
if (ringbuf_bytes_used(ph->ringbuf) && !l.continued) pagerhist_write_bytes(ph, (const uint8_t*)"\n", 1);
245233
pagerhist_write_bytes(ph, (const uint8_t*)"\x1b[m", 3);
246234
if (pagerhist_write_ucs4(ph, as_ansi_buf->buf, as_ansi_buf->len)) pagerhist_write_bytes(ph, (const uint8_t*)"\r", 1);
247235
}
@@ -335,30 +323,28 @@ static inline Line*
335323
get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, index_of(self, self->count - y - 1), l); return l; }
336324

337325
static inline char_type
338-
pagerhist_read_char(PagerHistoryBuf *ph, size_t pos, unsigned *count, uint8_t record[8]) {
326+
pagerhist_remove_char(PagerHistoryBuf *ph, unsigned *count, uint8_t record[8]) {
339327
uint32_t codep, state = UTF8_ACCEPT;
340328
*count = 0;
341-
while (true) {
342-
decode_utf8(&state, &codep, ph->buffer[pos]);
343-
record[(*count)++] = ph->buffer[pos];
329+
while (ringbuf_bytes_used(ph->ringbuf)) {
330+
ringbuf_memmove_from(&record[*count], ph->ringbuf, 1);
331+
decode_utf8(&state, &codep, record[*count]);
332+
*count += 1;
344333
if (state == UTF8_REJECT) { codep = 0; break; }
345334
if (state == UTF8_ACCEPT) break;
346-
pos = pos == ph->buffer_size - 1 ? 0 : (pos + 1);
347335
}
348336
return codep;
349337
}
350338

351339
static void
352340
pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) {
353341
PagerHistoryBuf *ph = self->pagerhist;
354-
if (!ph->length) return;
342+
if (!ph->ringbuf || !ringbuf_bytes_used(ph->ringbuf)) return;
355343
PagerHistoryBuf *nph = PyMem_Calloc(sizeof(PagerHistoryBuf), 1);
356344
if (!nph) return;
357-
nph->buffer_size = ph->buffer_size;
358-
nph->max_sz = ph->max_sz;
359-
nph->buffer = PyMem_Malloc(nph->buffer_size);
360-
if (!nph->buffer) { PyMem_Free(nph); return ; }
361-
size_t i = 0, pos;
345+
nph->maximum_size = ph->maximum_size;
346+
nph->ringbuf = ringbuf_new(MIN(ph->maximum_size, ringbuf_capacity(ph->ringbuf) + 4096));
347+
if (!nph->ringbuf) { PyMem_Free(nph); return ; }
362348
ssize_t ch_width = 0;
363349
unsigned count;
364350
uint8_t record[8];
@@ -367,11 +353,6 @@ pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) {
367353
WCSState wcs_state;
368354
initialize_wcs_state(&wcs_state);
369355

370-
#define READ_CHAR(ch) { \
371-
ch = pagerhist_read_char(ph, pos, &count, record); \
372-
i += count; pos += count; \
373-
if (pos >= ph->buffer_size) pos = pos - ph->buffer_size; \
374-
}
375356
#define WRITE_CHAR() { \
376357
if (num_in_current_line + ch_width > cells_in_line) { \
377358
pagerhist_write_bytes(nph, (const uint8_t*)"\r", 1); \
@@ -381,10 +362,8 @@ pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) {
381362
pagerhist_write_bytes(nph, record, count); \
382363
}
383364

384-
for (i = 0; i < ph->length;) {
385-
pos = ph->start + i;
386-
if (pos >= ph->buffer_size) pos = pos - ph->buffer_size;
387-
READ_CHAR(ch);
365+
while (ringbuf_bytes_used(ph->ringbuf)) {
366+
ch = pagerhist_remove_char(ph, &count, record);
388367
if (ch == '\n') {
389368
initialize_wcs_state(&wcs_state);
390369
ch_width = 1;
@@ -397,12 +376,12 @@ pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) {
397376
}
398377
free_pagerhist(self);
399378
self->pagerhist = nph;
400-
#undef READ_CHAR
379+
#undef WRITE_CHAR
401380
}
402381

403382
static PyObject*
404383
pagerhist_write(HistoryBuf *self, PyObject *what) {
405-
if (self->pagerhist && self->pagerhist->max_sz) {
384+
if (self->pagerhist && self->pagerhist->maximum_size) {
406385
if (PyBytes_Check(what)) pagerhist_write_bytes(self->pagerhist, (const uint8_t*)PyBytes_AS_STRING(what), PyBytes_GET_SIZE(what));
407386
else if (PyUnicode_Check(what) && PyUnicode_READY(what) == 0) {
408387
Py_UCS4 *buf = PyUnicode_AsUCS4Copy(what);
@@ -418,19 +397,17 @@ pagerhist_write(HistoryBuf *self, PyObject *what) {
418397
static PyObject*
419398
pagerhist_as_bytes(HistoryBuf *self, PyObject *args UNUSED) {
420399
PagerHistoryBuf *ph = self->pagerhist;
421-
if (!ph || !ph->length) return PyBytes_FromStringAndSize("", 0);
400+
if (!ph || !ringbuf_bytes_used(ph->ringbuf)) return PyBytes_FromStringAndSize("", 0);
422401
pagerhist_ensure_start_is_valid_utf8(ph);
423402
if (ph->rewrap_needed) pagerhist_rewrap_to(self, self->xnum);
424403

425404
Line l = {.xnum=self->xnum}; get_line(self, 0, &l);
426-
size_t sz = ph->length;
405+
size_t sz = ringbuf_bytes_used(ph->ringbuf);
427406
if (!l.continued) sz += 1;
428407
PyObject *ans = PyBytes_FromStringAndSize(NULL, sz);
429408
if (!ans) return NULL;
430409
uint8_t *buf = (uint8_t*)PyBytes_AS_STRING(ans);
431-
size_t copied = MIN(ph->length, ph->buffer_size - ph->start);
432-
if (copied) memcpy(buf, ph->buffer + ph->start, copied);
433-
if (copied < ph->length) memcpy(buf + copied, ph->buffer, (ph->length - copied));
410+
ringbuf_memcpy_from(buf, ph->ringbuf, sz);
434411
if (!l.continued) buf[sz-1] = '\n';
435412
return ans;
436413
}
@@ -561,7 +538,7 @@ void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other, ANSIBuf *as_ansi_buf
561538
other->count = self->count; other->start_of_data = self->start_of_data;
562539
return;
563540
}
564-
if (other->pagerhist && other->xnum != self->xnum && other->pagerhist->length)
541+
if (other->pagerhist && other->xnum != self->xnum && ringbuf_bytes_used(other->pagerhist->ringbuf))
565542
other->pagerhist->rewrap_needed = true;
566543
other->count = 0; other->start_of_data = 0;
567544
index_type x = 0, y = 0;

0 commit comments

Comments
 (0)