Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * test_aio.c
4 : * Helpers to write tests for AIO
5 : *
6 : * This module provides interface functions for C functionality to SQL, to
7 : * make it possible to test AIO related behavior in a targeted way from SQL.
8 : * It'd not generally be safe to export these functions to SQL, but for a test
9 : * that's fine.
10 : *
11 : * Copyright (c) 2020-2025, PostgreSQL Global Development Group
12 : *
13 : * IDENTIFICATION
14 : * src/test/modules/test_aio/test_aio.c
15 : *
16 : *-------------------------------------------------------------------------
17 : */
18 :
19 : #include "postgres.h"
20 :
21 : #include "access/relation.h"
22 : #include "fmgr.h"
23 : #include "storage/aio.h"
24 : #include "storage/aio_internal.h"
25 : #include "storage/buf_internals.h"
26 : #include "storage/bufmgr.h"
27 : #include "storage/checksum.h"
28 : #include "storage/ipc.h"
29 : #include "storage/lwlock.h"
30 : #include "utils/builtins.h"
31 : #include "utils/injection_point.h"
32 : #include "utils/rel.h"
33 :
34 :
35 4 : PG_MODULE_MAGIC;
36 :
37 :
38 : typedef struct InjIoErrorState
39 : {
40 : bool enabled_short_read;
41 : bool enabled_reopen;
42 :
43 : bool short_read_result_set;
44 : int short_read_result;
45 : } InjIoErrorState;
46 :
47 : static InjIoErrorState *inj_io_error_state;
48 :
49 : /* Shared memory init callbacks */
50 : static shmem_request_hook_type prev_shmem_request_hook = NULL;
51 : static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
52 :
53 :
54 : static PgAioHandle *last_handle;
55 :
56 :
57 :
58 : static void
59 4 : test_aio_shmem_request(void)
60 : {
61 4 : if (prev_shmem_request_hook)
62 0 : prev_shmem_request_hook();
63 :
64 4 : RequestAddinShmemSpace(sizeof(InjIoErrorState));
65 4 : }
66 :
67 : static void
68 4 : test_aio_shmem_startup(void)
69 : {
70 : bool found;
71 :
72 4 : if (prev_shmem_startup_hook)
73 0 : prev_shmem_startup_hook();
74 :
75 : /* Create or attach to the shared memory state */
76 4 : LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
77 :
78 4 : inj_io_error_state = ShmemInitStruct("injection_points",
79 : sizeof(InjIoErrorState),
80 : &found);
81 :
82 4 : if (!found)
83 : {
84 : /* First time through, initialize */
85 4 : inj_io_error_state->enabled_short_read = false;
86 4 : inj_io_error_state->enabled_reopen = false;
87 :
88 : #ifdef USE_INJECTION_POINTS
89 4 : InjectionPointAttach("aio-process-completion-before-shared",
90 : "test_aio",
91 : "inj_io_short_read",
92 : NULL,
93 : 0);
94 4 : InjectionPointLoad("aio-process-completion-before-shared");
95 :
96 4 : InjectionPointAttach("aio-worker-after-reopen",
97 : "test_aio",
98 : "inj_io_reopen",
99 : NULL,
100 : 0);
101 4 : InjectionPointLoad("aio-worker-after-reopen");
102 :
103 : #endif
104 : }
105 : else
106 : {
107 : /*
108 : * Pre-load the injection points now, so we can call them in a
109 : * critical section.
110 : */
111 : #ifdef USE_INJECTION_POINTS
112 0 : InjectionPointLoad("aio-process-completion-before-shared");
113 0 : InjectionPointLoad("aio-worker-after-reopen");
114 0 : elog(LOG, "injection point loaded");
115 : #endif
116 : }
117 :
118 4 : LWLockRelease(AddinShmemInitLock);
119 4 : }
120 :
121 : void
122 4 : _PG_init(void)
123 : {
124 4 : if (!process_shared_preload_libraries_in_progress)
125 0 : return;
126 :
127 4 : prev_shmem_request_hook = shmem_request_hook;
128 4 : shmem_request_hook = test_aio_shmem_request;
129 4 : prev_shmem_startup_hook = shmem_startup_hook;
130 4 : shmem_startup_hook = test_aio_shmem_startup;
131 : }
132 :
133 :
134 12 : PG_FUNCTION_INFO_V1(errno_from_string);
135 : Datum
136 8 : errno_from_string(PG_FUNCTION_ARGS)
137 : {
138 8 : const char *sym = text_to_cstring(PG_GETARG_TEXT_PP(0));
139 :
140 8 : if (strcmp(sym, "EIO") == 0)
141 4 : PG_RETURN_INT32(EIO);
142 4 : else if (strcmp(sym, "EAGAIN") == 0)
143 0 : PG_RETURN_INT32(EAGAIN);
144 4 : else if (strcmp(sym, "EINTR") == 0)
145 0 : PG_RETURN_INT32(EINTR);
146 4 : else if (strcmp(sym, "ENOSPC") == 0)
147 0 : PG_RETURN_INT32(ENOSPC);
148 4 : else if (strcmp(sym, "EROFS") == 0)
149 4 : PG_RETURN_INT32(EROFS);
150 :
151 0 : ereport(ERROR,
152 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
153 : errmsg_internal("%s is not a supported errno value", sym));
154 : PG_RETURN_INT32(0);
155 : }
156 :
157 16 : PG_FUNCTION_INFO_V1(grow_rel);
158 : Datum
159 12 : grow_rel(PG_FUNCTION_ARGS)
160 : {
161 12 : Oid relid = PG_GETARG_OID(0);
162 12 : uint32 nblocks = PG_GETARG_UINT32(1);
163 : Relation rel;
164 : #define MAX_BUFFERS_TO_EXTEND_BY 64
165 : Buffer victim_buffers[MAX_BUFFERS_TO_EXTEND_BY];
166 :
167 12 : rel = relation_open(relid, AccessExclusiveLock);
168 :
169 24 : while (nblocks > 0)
170 : {
171 : uint32 extend_by_pages;
172 :
173 12 : extend_by_pages = Min(nblocks, MAX_BUFFERS_TO_EXTEND_BY);
174 :
175 12 : ExtendBufferedRelBy(BMR_REL(rel),
176 : MAIN_FORKNUM,
177 : NULL,
178 : 0,
179 : extend_by_pages,
180 : victim_buffers,
181 : &extend_by_pages);
182 :
183 12 : nblocks -= extend_by_pages;
184 :
185 156 : for (uint32 i = 0; i < extend_by_pages; i++)
186 : {
187 144 : ReleaseBuffer(victim_buffers[i]);
188 : }
189 : }
190 :
191 12 : relation_close(rel, NoLock);
192 :
193 12 : PG_RETURN_VOID();
194 : }
195 :
196 36 : PG_FUNCTION_INFO_V1(modify_rel_block);
197 : Datum
198 140 : modify_rel_block(PG_FUNCTION_ARGS)
199 : {
200 140 : Oid relid = PG_GETARG_OID(0);
201 140 : BlockNumber blkno = PG_GETARG_UINT32(1);
202 140 : bool zero = PG_GETARG_BOOL(2);
203 140 : bool corrupt_header = PG_GETARG_BOOL(3);
204 140 : bool corrupt_checksum = PG_GETARG_BOOL(4);
205 140 : Page page = palloc_aligned(BLCKSZ, PG_IO_ALIGN_SIZE, 0);
206 : bool flushed;
207 : Relation rel;
208 : Buffer buf;
209 : PageHeader ph;
210 :
211 140 : rel = relation_open(relid, AccessExclusiveLock);
212 :
213 140 : buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno,
214 : RBM_ZERO_ON_ERROR, NULL);
215 :
216 140 : LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
217 :
218 : /*
219 : * copy the page to local memory, seems nicer than to directly modify in
220 : * the buffer pool.
221 : */
222 140 : memcpy(page, BufferGetPage(buf), BLCKSZ);
223 :
224 140 : LockBuffer(buf, BUFFER_LOCK_UNLOCK);
225 :
226 140 : ReleaseBuffer(buf);
227 :
228 : /*
229 : * Don't want to have a buffer in-memory that's marked valid where the
230 : * on-disk contents are invalid. Particularly not if the in-memory buffer
231 : * could be dirty...
232 : *
233 : * While we hold an AEL on the relation nobody else should be able to read
234 : * the buffer in.
235 : *
236 : * NB: This is probably racy, better don't copy this to non-test code.
237 : */
238 140 : if (BufferIsLocal(buf))
239 36 : InvalidateLocalBuffer(GetLocalBufferDescriptor(-buf - 1), true);
240 : else
241 104 : EvictUnpinnedBuffer(buf, &flushed);
242 :
243 : /*
244 : * Now modify the page as asked for by the caller.
245 : */
246 140 : if (zero)
247 32 : memset(page, 0, BufferGetPageSize(buf));
248 :
249 140 : if (PageIsEmpty(page) && (corrupt_header || corrupt_checksum))
250 32 : PageInit(page, BufferGetPageSize(buf), 0);
251 :
252 140 : ph = (PageHeader) page;
253 :
254 140 : if (corrupt_header)
255 64 : ph->pd_special = BLCKSZ + 1;
256 :
257 140 : if (corrupt_checksum)
258 : {
259 52 : bool successfully_corrupted = 0;
260 :
261 : /*
262 : * Any single modification of the checksum could just end up being
263 : * valid again, due to e.g. corrupt_header changing the data in a way
264 : * that'd result in the "corrupted" checksum, or the checksum already
265 : * being invalid. Retry in that, unlikely, case.
266 : */
267 52 : for (int i = 0; i < 100; i++)
268 : {
269 : uint16 verify_checksum;
270 : uint16 old_checksum;
271 :
272 52 : old_checksum = ph->pd_checksum;
273 52 : ph->pd_checksum = old_checksum + 1;
274 :
275 52 : elog(LOG, "corrupting checksum of blk %u from %u to %u",
276 : blkno, old_checksum, ph->pd_checksum);
277 :
278 52 : verify_checksum = pg_checksum_page(page, blkno);
279 52 : if (verify_checksum != ph->pd_checksum)
280 : {
281 52 : successfully_corrupted = true;
282 52 : break;
283 : }
284 : }
285 :
286 52 : if (!successfully_corrupted)
287 0 : elog(ERROR, "could not corrupt checksum, what's going on?");
288 : }
289 : else
290 : {
291 88 : PageSetChecksumInplace(page, blkno);
292 : }
293 :
294 140 : smgrwrite(RelationGetSmgr(rel),
295 : MAIN_FORKNUM, blkno, page, true);
296 :
297 140 : relation_close(rel, NoLock);
298 :
299 140 : PG_RETURN_VOID();
300 : }
301 :
302 : /*
303 : * Ensures a buffer for rel & blkno is in shared buffers, without actually
304 : * caring about the buffer contents. Used to set up test scenarios.
305 : */
306 : static Buffer
307 260 : create_toy_buffer(Relation rel, BlockNumber blkno)
308 : {
309 : Buffer buf;
310 : BufferDesc *buf_hdr;
311 : uint32 buf_state;
312 260 : bool was_pinned = false;
313 260 : uint32 unset_bits = 0;
314 :
315 : /* place buffer in shared buffers without erroring out */
316 260 : buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_ZERO_AND_LOCK, NULL);
317 260 : LockBuffer(buf, BUFFER_LOCK_UNLOCK);
318 :
319 260 : if (RelationUsesLocalBuffers(rel))
320 : {
321 88 : buf_hdr = GetLocalBufferDescriptor(-buf - 1);
322 88 : buf_state = pg_atomic_read_u32(&buf_hdr->state);
323 : }
324 : else
325 : {
326 172 : buf_hdr = GetBufferDescriptor(buf - 1);
327 172 : buf_state = LockBufHdr(buf_hdr);
328 : }
329 :
330 : /*
331 : * We should be the only backend accessing this buffer. This is just a
332 : * small bit of belt-and-suspenders defense, none of this code should ever
333 : * run in a cluster with real data.
334 : */
335 260 : if (BUF_STATE_GET_REFCOUNT(buf_state) > 1)
336 0 : was_pinned = true;
337 : else
338 260 : unset_bits |= BM_VALID | BM_DIRTY;
339 :
340 260 : if (RelationUsesLocalBuffers(rel))
341 : {
342 88 : buf_state &= ~unset_bits;
343 88 : pg_atomic_unlocked_write_u32(&buf_hdr->state, buf_state);
344 : }
345 : else
346 : {
347 172 : UnlockBufHdrExt(buf_hdr, buf_state, 0, unset_bits, 0);
348 : }
349 :
350 260 : if (was_pinned)
351 0 : elog(ERROR, "toy buffer %d was already pinned",
352 : buf);
353 :
354 260 : return buf;
355 : }
356 :
357 : /*
358 : * A "low level" read. This does similar things to what
359 : * StartReadBuffers()/WaitReadBuffers() do, but provides more control (and
360 : * less sanity).
361 : */
362 40 : PG_FUNCTION_INFO_V1(read_rel_block_ll);
363 : Datum
364 144 : read_rel_block_ll(PG_FUNCTION_ARGS)
365 : {
366 144 : Oid relid = PG_GETARG_OID(0);
367 144 : BlockNumber blkno = PG_GETARG_UINT32(1);
368 144 : int nblocks = PG_GETARG_INT32(2);
369 144 : bool wait_complete = PG_GETARG_BOOL(3);
370 144 : bool batchmode_enter = PG_GETARG_BOOL(4);
371 144 : bool call_smgrreleaseall = PG_GETARG_BOOL(5);
372 144 : bool batchmode_exit = PG_GETARG_BOOL(6);
373 144 : bool zero_on_error = PG_GETARG_BOOL(7);
374 : Relation rel;
375 : Buffer bufs[PG_IOV_MAX];
376 : BufferDesc *buf_hdrs[PG_IOV_MAX];
377 : Page pages[PG_IOV_MAX];
378 144 : uint8 srb_flags = 0;
379 : PgAioReturn ior;
380 : PgAioHandle *ioh;
381 : PgAioWaitRef iow;
382 : SMgrRelation smgr;
383 :
384 144 : if (nblocks <= 0 || nblocks > PG_IOV_MAX)
385 0 : elog(ERROR, "nblocks is out of range");
386 :
387 144 : rel = relation_open(relid, AccessExclusiveLock);
388 :
389 392 : for (int i = 0; i < nblocks; i++)
390 : {
391 248 : bufs[i] = create_toy_buffer(rel, blkno + i);
392 248 : pages[i] = BufferGetBlock(bufs[i]);
393 248 : buf_hdrs[i] = BufferIsLocal(bufs[i]) ?
394 248 : GetLocalBufferDescriptor(-bufs[i] - 1) :
395 164 : GetBufferDescriptor(bufs[i] - 1);
396 : }
397 :
398 144 : smgr = RelationGetSmgr(rel);
399 :
400 144 : pgstat_prepare_report_checksum_failure(smgr->smgr_rlocator.locator.dbOid);
401 :
402 144 : ioh = pgaio_io_acquire(CurrentResourceOwner, &ior);
403 144 : pgaio_io_get_wref(ioh, &iow);
404 :
405 144 : if (RelationUsesLocalBuffers(rel))
406 : {
407 128 : for (int i = 0; i < nblocks; i++)
408 84 : StartLocalBufferIO(buf_hdrs[i], true, false);
409 44 : pgaio_io_set_flag(ioh, PGAIO_HF_REFERENCES_LOCAL);
410 : }
411 : else
412 : {
413 264 : for (int i = 0; i < nblocks; i++)
414 164 : StartBufferIO(buf_hdrs[i], true, false);
415 : }
416 :
417 144 : pgaio_io_set_handle_data_32(ioh, (uint32 *) bufs, nblocks);
418 :
419 144 : if (zero_on_error | zero_damaged_pages)
420 44 : srb_flags |= READ_BUFFERS_ZERO_ON_ERROR;
421 144 : if (ignore_checksum_failure)
422 20 : srb_flags |= READ_BUFFERS_IGNORE_CHECKSUM_FAILURES;
423 :
424 144 : pgaio_io_register_callbacks(ioh,
425 144 : RelationUsesLocalBuffers(rel) ?
426 : PGAIO_HCB_LOCAL_BUFFER_READV :
427 : PGAIO_HCB_SHARED_BUFFER_READV,
428 : srb_flags);
429 :
430 144 : if (batchmode_enter)
431 8 : pgaio_enter_batchmode();
432 :
433 144 : smgrstartreadv(ioh, smgr, MAIN_FORKNUM, blkno,
434 : (void *) pages, nblocks);
435 :
436 144 : if (call_smgrreleaseall)
437 8 : smgrreleaseall();
438 :
439 144 : if (batchmode_exit)
440 8 : pgaio_exit_batchmode();
441 :
442 392 : for (int i = 0; i < nblocks; i++)
443 248 : ReleaseBuffer(bufs[i]);
444 :
445 144 : if (wait_complete)
446 : {
447 100 : pgaio_wref_wait(&iow);
448 :
449 100 : if (ior.result.status != PGAIO_RS_OK)
450 88 : pgaio_result_report(ior.result,
451 : &ior.target_data,
452 88 : ior.result.status == PGAIO_RS_ERROR ?
453 : ERROR : WARNING);
454 : }
455 :
456 100 : relation_close(rel, NoLock);
457 :
458 100 : PG_RETURN_VOID();
459 : }
460 :
461 22 : PG_FUNCTION_INFO_V1(invalidate_rel_block);
462 : Datum
463 278 : invalidate_rel_block(PG_FUNCTION_ARGS)
464 : {
465 278 : Oid relid = PG_GETARG_OID(0);
466 278 : BlockNumber blkno = PG_GETARG_UINT32(1);
467 : Relation rel;
468 : PrefetchBufferResult pr;
469 : Buffer buf;
470 :
471 278 : rel = relation_open(relid, AccessExclusiveLock);
472 :
473 : /*
474 : * This is a gross hack, but there's no other API exposed that allows to
475 : * get a buffer ID without actually reading the block in.
476 : */
477 278 : pr = PrefetchBuffer(rel, MAIN_FORKNUM, blkno);
478 278 : buf = pr.recent_buffer;
479 :
480 278 : if (BufferIsValid(buf))
481 : {
482 : /* if the buffer contents aren't valid, this'll return false */
483 250 : if (ReadRecentBuffer(rel->rd_locator, MAIN_FORKNUM, blkno, buf))
484 : {
485 238 : BufferDesc *buf_hdr = BufferIsLocal(buf) ?
486 64 : GetLocalBufferDescriptor(-buf - 1)
487 238 : : GetBufferDescriptor(buf - 1);
488 : bool flushed;
489 :
490 238 : LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
491 :
492 238 : if (pg_atomic_read_u32(&buf_hdr->state) & BM_DIRTY)
493 : {
494 144 : if (BufferIsLocal(buf))
495 44 : FlushLocalBuffer(buf_hdr, NULL);
496 : else
497 100 : FlushOneBuffer(buf);
498 : }
499 238 : LockBuffer(buf, BUFFER_LOCK_UNLOCK);
500 238 : ReleaseBuffer(buf);
501 :
502 238 : if (BufferIsLocal(buf))
503 64 : InvalidateLocalBuffer(GetLocalBufferDescriptor(-buf - 1), true);
504 174 : else if (!EvictUnpinnedBuffer(buf, &flushed))
505 0 : elog(ERROR, "couldn't evict");
506 : }
507 : }
508 :
509 278 : relation_close(rel, AccessExclusiveLock);
510 :
511 278 : PG_RETURN_VOID();
512 : }
513 :
514 12 : PG_FUNCTION_INFO_V1(buffer_create_toy);
515 : Datum
516 12 : buffer_create_toy(PG_FUNCTION_ARGS)
517 : {
518 12 : Oid relid = PG_GETARG_OID(0);
519 12 : BlockNumber blkno = PG_GETARG_UINT32(1);
520 : Relation rel;
521 : Buffer buf;
522 :
523 12 : rel = relation_open(relid, AccessExclusiveLock);
524 :
525 12 : buf = create_toy_buffer(rel, blkno);
526 12 : ReleaseBuffer(buf);
527 :
528 12 : relation_close(rel, NoLock);
529 :
530 12 : PG_RETURN_INT32(buf);
531 : }
532 :
533 16 : PG_FUNCTION_INFO_V1(buffer_call_start_io);
534 : Datum
535 40 : buffer_call_start_io(PG_FUNCTION_ARGS)
536 : {
537 40 : Buffer buf = PG_GETARG_INT32(0);
538 40 : bool for_input = PG_GETARG_BOOL(1);
539 40 : bool nowait = PG_GETARG_BOOL(2);
540 : bool can_start;
541 :
542 40 : if (BufferIsLocal(buf))
543 16 : can_start = StartLocalBufferIO(GetLocalBufferDescriptor(-buf - 1),
544 : for_input, nowait);
545 : else
546 24 : can_start = StartBufferIO(GetBufferDescriptor(buf - 1),
547 : for_input, nowait);
548 :
549 : /*
550 : * For tests we don't want the resowner release preventing us from
551 : * orchestrating odd scenarios.
552 : */
553 40 : if (can_start && !BufferIsLocal(buf))
554 12 : ResourceOwnerForgetBufferIO(CurrentResourceOwner,
555 : buf);
556 :
557 40 : ereport(LOG,
558 : errmsg("buffer %d after StartBufferIO: %s",
559 : buf, DebugPrintBufferRefcount(buf)),
560 : errhidestmt(true), errhidecontext(true));
561 :
562 40 : PG_RETURN_BOOL(can_start);
563 : }
564 :
565 16 : PG_FUNCTION_INFO_V1(buffer_call_terminate_io);
566 : Datum
567 20 : buffer_call_terminate_io(PG_FUNCTION_ARGS)
568 : {
569 20 : Buffer buf = PG_GETARG_INT32(0);
570 20 : bool for_input = PG_GETARG_BOOL(1);
571 20 : bool succeed = PG_GETARG_BOOL(2);
572 20 : bool io_error = PG_GETARG_BOOL(3);
573 20 : bool release_aio = PG_GETARG_BOOL(4);
574 20 : bool clear_dirty = false;
575 20 : uint32 set_flag_bits = 0;
576 :
577 20 : if (io_error)
578 0 : set_flag_bits |= BM_IO_ERROR;
579 :
580 20 : if (for_input)
581 : {
582 20 : clear_dirty = false;
583 :
584 20 : if (succeed)
585 8 : set_flag_bits |= BM_VALID;
586 : }
587 : else
588 : {
589 0 : if (succeed)
590 0 : clear_dirty = true;
591 : }
592 :
593 20 : ereport(LOG,
594 : errmsg("buffer %d before Terminate[Local]BufferIO: %s",
595 : buf, DebugPrintBufferRefcount(buf)),
596 : errhidestmt(true), errhidecontext(true));
597 :
598 20 : if (BufferIsLocal(buf))
599 8 : TerminateLocalBufferIO(GetLocalBufferDescriptor(-buf - 1),
600 : clear_dirty, set_flag_bits, release_aio);
601 : else
602 12 : TerminateBufferIO(GetBufferDescriptor(buf - 1),
603 : clear_dirty, set_flag_bits, false, release_aio);
604 :
605 20 : ereport(LOG,
606 : errmsg("buffer %d after Terminate[Local]BufferIO: %s",
607 : buf, DebugPrintBufferRefcount(buf)),
608 : errhidestmt(true), errhidecontext(true));
609 :
610 20 : PG_RETURN_VOID();
611 : }
612 :
613 12 : PG_FUNCTION_INFO_V1(handle_get);
614 : Datum
615 24 : handle_get(PG_FUNCTION_ARGS)
616 : {
617 24 : last_handle = pgaio_io_acquire(CurrentResourceOwner, NULL);
618 :
619 24 : PG_RETURN_VOID();
620 : }
621 :
622 12 : PG_FUNCTION_INFO_V1(handle_release_last);
623 : Datum
624 8 : handle_release_last(PG_FUNCTION_ARGS)
625 : {
626 8 : if (!last_handle)
627 0 : elog(ERROR, "no handle");
628 :
629 8 : pgaio_io_release(last_handle);
630 :
631 4 : PG_RETURN_VOID();
632 : }
633 :
634 12 : PG_FUNCTION_INFO_V1(handle_get_and_error);
635 : Datum
636 12 : handle_get_and_error(PG_FUNCTION_ARGS)
637 : {
638 12 : pgaio_io_acquire(CurrentResourceOwner, NULL);
639 :
640 12 : elog(ERROR, "as you command");
641 : PG_RETURN_VOID();
642 : }
643 :
644 12 : PG_FUNCTION_INFO_V1(handle_get_twice);
645 : Datum
646 4 : handle_get_twice(PG_FUNCTION_ARGS)
647 : {
648 4 : pgaio_io_acquire(CurrentResourceOwner, NULL);
649 4 : pgaio_io_acquire(CurrentResourceOwner, NULL);
650 :
651 0 : PG_RETURN_VOID();
652 : }
653 :
654 12 : PG_FUNCTION_INFO_V1(handle_get_release);
655 : Datum
656 12 : handle_get_release(PG_FUNCTION_ARGS)
657 : {
658 : PgAioHandle *handle;
659 :
660 12 : handle = pgaio_io_acquire(CurrentResourceOwner, NULL);
661 12 : pgaio_io_release(handle);
662 :
663 12 : PG_RETURN_VOID();
664 : }
665 :
666 12 : PG_FUNCTION_INFO_V1(batch_start);
667 : Datum
668 12 : batch_start(PG_FUNCTION_ARGS)
669 : {
670 12 : pgaio_enter_batchmode();
671 12 : PG_RETURN_VOID();
672 : }
673 :
674 12 : PG_FUNCTION_INFO_V1(batch_end);
675 : Datum
676 4 : batch_end(PG_FUNCTION_ARGS)
677 : {
678 4 : pgaio_exit_batchmode();
679 4 : PG_RETURN_VOID();
680 : }
681 :
682 : #ifdef USE_INJECTION_POINTS
683 : extern PGDLLEXPORT void inj_io_short_read(const char *name,
684 : const void *private_data,
685 : void *arg);
686 : extern PGDLLEXPORT void inj_io_reopen(const char *name,
687 : const void *private_data,
688 : void *arg);
689 :
690 : void
691 2546 : inj_io_short_read(const char *name, const void *private_data, void *arg)
692 : {
693 2546 : PgAioHandle *ioh = (PgAioHandle *) arg;
694 :
695 2546 : ereport(LOG,
696 : errmsg("short read injection point called, is enabled: %d",
697 : inj_io_error_state->enabled_reopen),
698 : errhidestmt(true), errhidecontext(true));
699 :
700 2546 : if (inj_io_error_state->enabled_short_read)
701 : {
702 : /*
703 : * Only shorten reads that are actually longer than the target size,
704 : * otherwise we can trigger over-reads.
705 : */
706 96 : if (inj_io_error_state->short_read_result_set
707 96 : && ioh->op == PGAIO_OP_READV
708 96 : && inj_io_error_state->short_read_result <= ioh->result)
709 : {
710 88 : struct iovec *iov = &pgaio_ctl->iovecs[ioh->iovec_off];
711 88 : int32 old_result = ioh->result;
712 88 : int32 new_result = inj_io_error_state->short_read_result;
713 88 : int32 processed = 0;
714 :
715 88 : ereport(LOG,
716 : errmsg("short read inject point, changing result from %d to %d",
717 : old_result, new_result),
718 : errhidestmt(true), errhidecontext(true));
719 :
720 : /*
721 : * The underlying IO actually completed OK, and thus the "invalid"
722 : * portion of the IOV actually contains valid data. That can hide
723 : * a lot of problems, e.g. if we were to wrongly mark a buffer,
724 : * that wasn't read according to the shortened-read, IO as valid,
725 : * the contents would look valid and we might miss a bug.
726 : *
727 : * To avoid that, iterate through the IOV and zero out the
728 : * "failed" portion of the IO.
729 : */
730 184 : for (int i = 0; i < ioh->op_data.read.iov_length; i++)
731 : {
732 96 : if (processed + iov[i].iov_len <= new_result)
733 68 : processed += iov[i].iov_len;
734 28 : else if (processed <= new_result)
735 : {
736 28 : uint32 ok_part = new_result - processed;
737 :
738 28 : memset((char *) iov[i].iov_base + ok_part, 0, iov[i].iov_len - ok_part);
739 28 : processed += iov[i].iov_len;
740 : }
741 : else
742 : {
743 0 : memset((char *) iov[i].iov_base, 0, iov[i].iov_len);
744 : }
745 : }
746 :
747 88 : ioh->result = new_result;
748 : }
749 : }
750 2546 : }
751 :
752 : void
753 692 : inj_io_reopen(const char *name, const void *private_data, void *arg)
754 : {
755 692 : ereport(LOG,
756 : errmsg("reopen injection point called, is enabled: %d",
757 : inj_io_error_state->enabled_reopen),
758 : errhidestmt(true), errhidecontext(true));
759 :
760 692 : if (inj_io_error_state->enabled_reopen)
761 2 : elog(ERROR, "injection point triggering failure to reopen ");
762 690 : }
763 : #endif
764 :
765 12 : PG_FUNCTION_INFO_V1(inj_io_short_read_attach);
766 : Datum
767 28 : inj_io_short_read_attach(PG_FUNCTION_ARGS)
768 : {
769 : #ifdef USE_INJECTION_POINTS
770 28 : inj_io_error_state->enabled_short_read = true;
771 28 : inj_io_error_state->short_read_result_set = !PG_ARGISNULL(0);
772 28 : if (inj_io_error_state->short_read_result_set)
773 28 : inj_io_error_state->short_read_result = PG_GETARG_INT32(0);
774 : #else
775 : elog(ERROR, "injection points not supported");
776 : #endif
777 :
778 28 : PG_RETURN_VOID();
779 : }
780 :
781 12 : PG_FUNCTION_INFO_V1(inj_io_short_read_detach);
782 : Datum
783 8 : inj_io_short_read_detach(PG_FUNCTION_ARGS)
784 : {
785 : #ifdef USE_INJECTION_POINTS
786 8 : inj_io_error_state->enabled_short_read = false;
787 : #else
788 : elog(ERROR, "injection points not supported");
789 : #endif
790 8 : PG_RETURN_VOID();
791 : }
792 :
793 10 : PG_FUNCTION_INFO_V1(inj_io_reopen_attach);
794 : Datum
795 2 : inj_io_reopen_attach(PG_FUNCTION_ARGS)
796 : {
797 : #ifdef USE_INJECTION_POINTS
798 2 : inj_io_error_state->enabled_reopen = true;
799 : #else
800 : elog(ERROR, "injection points not supported");
801 : #endif
802 :
803 2 : PG_RETURN_VOID();
804 : }
805 :
806 10 : PG_FUNCTION_INFO_V1(inj_io_reopen_detach);
807 : Datum
808 2 : inj_io_reopen_detach(PG_FUNCTION_ARGS)
809 : {
810 : #ifdef USE_INJECTION_POINTS
811 2 : inj_io_error_state->enabled_reopen = false;
812 : #else
813 : elog(ERROR, "injection points not supported");
814 : #endif
815 2 : PG_RETURN_VOID();
816 : }
|