19
19
#include "access/xloginsert.h"
20
20
#include "miscadmin.h"
21
21
#include "storage/predicate.h"
22
+ #include "utils/injection_point.h"
22
23
#include "utils/memutils.h"
23
24
#include "utils/rel.h"
24
25
@@ -28,6 +29,8 @@ static bool ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
28
29
Buffer childbuf , GinStatsData * buildStats );
29
30
static void ginFinishSplit (GinBtree btree , GinBtreeStack * stack ,
30
31
bool freestack , GinStatsData * buildStats );
32
+ static void ginFinishOldSplit (GinBtree btree , GinBtreeStack * stack ,
33
+ GinStatsData * buildStats , int access );
31
34
32
35
/*
33
36
* Lock buffer by needed method for search.
@@ -108,7 +111,7 @@ ginFindLeafPage(GinBtree btree, bool searchMode,
108
111
* encounter on the way.
109
112
*/
110
113
if (!searchMode && GinPageIsIncompleteSplit (page ))
111
- ginFinishSplit (btree , stack , false, NULL );
114
+ ginFinishOldSplit (btree , stack , NULL , access );
112
115
113
116
/*
114
117
* ok, page is correctly locked, we should check to move right ..,
@@ -128,7 +131,7 @@ ginFindLeafPage(GinBtree btree, bool searchMode,
128
131
page = BufferGetPage (stack -> buffer );
129
132
130
133
if (!searchMode && GinPageIsIncompleteSplit (page ))
131
- ginFinishSplit (btree , stack , false, NULL );
134
+ ginFinishOldSplit (btree , stack , NULL , access );
132
135
}
133
136
134
137
if (GinPageIsLeaf (page )) /* we found, return locked page */
@@ -164,8 +167,11 @@ ginFindLeafPage(GinBtree btree, bool searchMode,
164
167
* Step right from current page.
165
168
*
166
169
* The next page is locked first, before releasing the current page. This is
167
- * crucial to protect from concurrent page deletion (see comment in
168
- * ginDeletePage).
170
+ * crucial to prevent concurrent VACUUM from deleting a page that we are about
171
+ * to step to. (The lock-coupling isn't strictly necessary when we are
172
+ * traversing the tree to find an insert location, because page deletion grabs
173
+ * a cleanup lock on the root to prevent any concurrent inserts. See Page
174
+ * deletion section in the README. But there's no harm in doing it always.)
169
175
*/
170
176
Buffer
171
177
ginStepRight (Buffer buffer , Relation index , int lockmode )
@@ -262,7 +268,7 @@ ginFindParents(GinBtree btree, GinBtreeStack *stack)
262
268
ptr -> parent = root ;
263
269
ptr -> off = InvalidOffsetNumber ;
264
270
265
- ginFinishSplit (btree , ptr , false, NULL );
271
+ ginFinishOldSplit (btree , ptr , NULL , GIN_EXCLUSIVE );
266
272
}
267
273
268
274
leftmostBlkno = btree -> getLeftMostChild (btree , page );
@@ -291,7 +297,7 @@ ginFindParents(GinBtree btree, GinBtreeStack *stack)
291
297
ptr -> parent = root ;
292
298
ptr -> off = InvalidOffsetNumber ;
293
299
294
- ginFinishSplit (btree , ptr , false, NULL );
300
+ ginFinishOldSplit (btree , ptr , NULL , GIN_EXCLUSIVE );
295
301
}
296
302
}
297
303
@@ -670,22 +676,20 @@ ginFinishSplit(GinBtree btree, GinBtreeStack *stack, bool freestack,
670
676
bool done ;
671
677
bool first = true;
672
678
673
- /*
674
- * freestack == false when we encounter an incompletely split page during
675
- * a scan, while freestack == true is used in the normal scenario that a
676
- * split is finished right after the initial insert.
677
- */
678
- if (!freestack )
679
- elog (DEBUG1 , "finishing incomplete split of block %u in gin index \"%s\"" ,
680
- stack -> blkno , RelationGetRelationName (btree -> index ));
681
-
682
679
/* this loop crawls up the stack until the insertion is complete */
683
680
do
684
681
{
685
682
GinBtreeStack * parent = stack -> parent ;
686
683
void * insertdata ;
687
684
BlockNumber updateblkno ;
688
685
686
+ #ifdef USE_INJECTION_POINTS
687
+ if (GinPageIsLeaf (BufferGetPage (stack -> buffer )))
688
+ INJECTION_POINT ("gin-leave-leaf-split-incomplete" );
689
+ else
690
+ INJECTION_POINT ("gin-leave-internal-split-incomplete" );
691
+ #endif
692
+
689
693
/* search parent to lock */
690
694
LockBuffer (parent -> buffer , GIN_EXCLUSIVE );
691
695
@@ -699,7 +703,7 @@ ginFinishSplit(GinBtree btree, GinBtreeStack *stack, bool freestack,
699
703
* would fail.
700
704
*/
701
705
if (GinPageIsIncompleteSplit (BufferGetPage (parent -> buffer )))
702
- ginFinishSplit (btree , parent , false, buildStats );
706
+ ginFinishOldSplit (btree , parent , buildStats , GIN_EXCLUSIVE );
703
707
704
708
/* move right if it's needed */
705
709
page = BufferGetPage (parent -> buffer );
@@ -723,7 +727,7 @@ ginFinishSplit(GinBtree btree, GinBtreeStack *stack, bool freestack,
723
727
page = BufferGetPage (parent -> buffer );
724
728
725
729
if (GinPageIsIncompleteSplit (BufferGetPage (parent -> buffer )))
726
- ginFinishSplit (btree , parent , false, buildStats );
730
+ ginFinishOldSplit (btree , parent , buildStats , GIN_EXCLUSIVE );
727
731
}
728
732
729
733
/* insert the downlink */
@@ -759,6 +763,43 @@ ginFinishSplit(GinBtree btree, GinBtreeStack *stack, bool freestack,
759
763
freeGinBtreeStack (stack );
760
764
}
761
765
766
+ /*
767
+ * An entry point to ginFinishSplit() that is used when we stumble upon an
768
+ * existing incompletely split page in the tree, as opposed to completing a
769
+ * split that we just made outselves. The difference is that stack->buffer may
770
+ * be merely share-locked on entry, and will be upgraded to exclusive mode.
771
+ *
772
+ * Note: Upgrading the lock momentarily releases it. Doing that in a scan
773
+ * would not be OK, because a concurrent VACUUM might delete the page while
774
+ * we're not holding the lock. It's OK in an insert, though, because VACUUM
775
+ * has a different mechanism that prevents it from running concurrently with
776
+ * inserts. (Namely, it holds a cleanup lock on the root.)
777
+ */
778
+ static void
779
+ ginFinishOldSplit (GinBtree btree , GinBtreeStack * stack , GinStatsData * buildStats , int access )
780
+ {
781
+ INJECTION_POINT ("gin-finish-incomplete-split" );
782
+ elog (DEBUG1 , "finishing incomplete split of block %u in gin index \"%s\"" ,
783
+ stack -> blkno , RelationGetRelationName (btree -> index ));
784
+
785
+ if (access == GIN_SHARE )
786
+ {
787
+ LockBuffer (stack -> buffer , GIN_UNLOCK );
788
+ LockBuffer (stack -> buffer , GIN_EXCLUSIVE );
789
+
790
+ if (!GinPageIsIncompleteSplit (BufferGetPage (stack -> buffer )))
791
+ {
792
+ /*
793
+ * Someone else already completed the split while we were not
794
+ * holding the lock.
795
+ */
796
+ return ;
797
+ }
798
+ }
799
+
800
+ ginFinishSplit (btree , stack , false, buildStats );
801
+ }
802
+
762
803
/*
763
804
* Insert a value to tree described by stack.
764
805
*
@@ -779,7 +820,7 @@ ginInsertValue(GinBtree btree, GinBtreeStack *stack, void *insertdata,
779
820
780
821
/* If the leaf page was incompletely split, finish the split first */
781
822
if (GinPageIsIncompleteSplit (BufferGetPage (stack -> buffer )))
782
- ginFinishSplit (btree , stack , false, buildStats );
823
+ ginFinishOldSplit (btree , stack , buildStats , GIN_EXCLUSIVE );
783
824
784
825
done = ginPlaceToPage (btree , stack ,
785
826
insertdata , InvalidBlockNumber ,
0 commit comments