From e5f3745578ad3cf65e00714024781b5ae82de1a6 Mon Sep 17 00:00:00 2001
From: Arthur Zakirov <z-arthur@yandex.ru>
Date: Thu, 17 Jan 2019 15:38:09 +0300
Subject: [PATCH 3/4] Retrieve-shared-location-for-dict

---
 src/backend/commands/tsearchcmds.c |  11 +
 src/backend/storage/ipc/ipci.c     |   7 +
 src/backend/tsearch/Makefile       |   2 +-
 src/backend/tsearch/ts_shared.c    | 494 +++++++++++++++++++++++++++++
 src/backend/utils/cache/ts_cache.c |  51 +++
 src/include/storage/lwlock.h       |   2 +
 src/include/tsearch/ts_cache.h     |   3 +
 src/include/tsearch/ts_shared.h    |  28 ++
 8 files changed, 597 insertions(+), 1 deletion(-)
 create mode 100644 src/backend/tsearch/ts_shared.c
 create mode 100644 src/include/tsearch/ts_shared.h

diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index 30c5eb72a2..90ad24c019 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -40,6 +40,7 @@
 #include "nodes/makefuncs.h"
 #include "parser/parse_func.h"
 #include "tsearch/ts_cache.h"
+#include "tsearch/ts_shared.h"
 #include "tsearch/ts_utils.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -528,6 +529,11 @@ RemoveTSDictionaryById(Oid dictId)
 
 	CatalogTupleDelete(relation, &tup->t_self);
 
+	ts_dict_shmem_release(dictId,
+						  HeapTupleHeaderGetRawXmin(tup->t_data),
+						  HeapTupleHeaderGetRawXmax(tup->t_data),
+						  tup->t_self, true);
+
 	ReleaseSysCache(tup);
 
 	table_close(relation, RowExclusiveLock);
@@ -637,6 +643,11 @@ AlterTSDictionary(AlterTSDictionaryStmt *stmt)
 
 	ObjectAddressSet(address, TSDictionaryRelationId, dictId);
 
+	ts_dict_shmem_release(dictId,
+						  HeapTupleHeaderGetRawXmin(tup->t_data),
+						  HeapTupleHeaderGetRawXmax(tup->t_data),
+						  tup->t_self, true);
+
 	/*
 	 * NOTE: because we only support altering the options, not the template,
 	 * there is no need to update dependencies.  This might have to change if
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 5965d3620f..029354b16c 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -44,6 +44,7 @@
 #include "storage/procsignal.h"
 #include "storage/sinvaladt.h"
 #include "storage/spin.h"
+#include "tsearch/ts_shared.h"
 #include "utils/snapmgr.h"
 
 /* GUCs */
@@ -150,6 +151,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, TsearchShmemSize());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -270,6 +272,11 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 	SyncScanShmemInit();
 	AsyncShmemInit();
 
+	/*
+	 * Set up shared memory to tsearch
+	 */
+	TsearchShmemInit();
+
 #ifdef EXEC_BACKEND
 
 	/*
diff --git a/src/backend/tsearch/Makefile b/src/backend/tsearch/Makefile
index 62d8bb3254..0b25c20fb0 100644
--- a/src/backend/tsearch/Makefile
+++ b/src/backend/tsearch/Makefile
@@ -26,7 +26,7 @@ DICTFILES_PATH=$(addprefix dicts/,$(DICTFILES))
 OBJS = ts_locale.o ts_parse.o wparser.o wparser_def.o dict.o \
 	dict_simple.o dict_synonym.o dict_thesaurus.o \
 	dict_ispell.o regis.o spell.o \
-	to_tsany.o ts_selfuncs.o ts_typanalyze.o ts_utils.o
+	to_tsany.o ts_selfuncs.o ts_shared.o ts_typanalyze.o ts_utils.o
 
 include $(top_srcdir)/src/backend/common.mk
 
diff --git a/src/backend/tsearch/ts_shared.c b/src/backend/tsearch/ts_shared.c
new file mode 100644
index 0000000000..d84fcb5eff
--- /dev/null
+++ b/src/backend/tsearch/ts_shared.c
@@ -0,0 +1,494 @@
+/*-------------------------------------------------------------------------
+ *
+ * ts_shared.c
+ *	  tsearch shared memory management
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/tsearch/ts_shared.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "lib/dshash.h"
+#include "miscadmin.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/spin.h"
+#include "tsearch/ts_shared.h"
+#include "utils/hashutils.h"
+#include "utils/memutils.h"
+
+
+/*
+ * Hash table entries key.
+ */
+typedef struct
+{
+	Oid			db_id;
+	DictEntryData dict;
+} TsearchDictKey;
+
+/*
+ * Hash table entries representing shared dictionaries.
+ */
+typedef struct
+{
+	TsearchDictKey key;
+	dsm_handle	dict_dsm;
+
+	/*
+	 * We need a flag that the DSM segment is pinned/unpinned.  Otherwise we can
+	 * face double dsm_unpin_segment().
+	 */
+	bool		segment_ispinned;
+
+	slock_t		mutex;		/* protects the reference count */
+	uint32		refcnt;		/* number of mapped backends */
+} TsearchDictEntry;
+
+/*
+ * Compiled dictionary data stored within the hash table.
+ */
+typedef struct
+{
+	TsearchDictKey dict_key;	/* entry's key used to release the entry */
+	char		dict[FLEXIBLE_ARRAY_MEMBER];
+} TsearchDictData;
+
+#define TsearchDictDataHdrSize	MAXALIGN(offsetof(TsearchDictData, dict))
+
+static dshash_table *dict_table = NULL;
+
+/*
+ * Information about the main shmem segment, used to coordinate
+ * access to the hash table and dictionaries.
+ */
+typedef struct
+{
+	dsa_handle	area;
+	dshash_table_handle dict_table_handle;
+
+	LWLock		lock;
+} TsearchCtlData;
+
+static TsearchCtlData *tsearch_ctl;
+
+static int tsearch_dict_cmp(const void *a, const void *b, size_t size,
+							void *arg);
+static uint32 tsearch_dict_hash(const void *a, size_t size, void *arg);
+
+static void init_dict_table(void);
+static dsm_segment *dict_entry_init(TsearchDictKey *key,
+									TsearchDictEntry *entry, void *dict,
+									Size dict_size);
+static dsm_segment *dict_entry_attach(TsearchDictEntry *entry);
+static void dict_entry_on_detach(dsm_segment *segment, Datum datum);
+
+/* Parameters for dict_table */
+static const dshash_parameters dict_table_params ={
+	sizeof(TsearchDictKey),
+	sizeof(TsearchDictEntry),
+	tsearch_dict_cmp,
+	tsearch_dict_hash,
+	LWTRANCHE_TSEARCH_TABLE
+};
+
+/*
+ * Build the dictionary using allocate_cb callback.
+ *
+ * Firstly try to find the dictionary in shared hash table. If it was built by
+ * someone earlier just return its location in DSM.
+ *
+ * init_data: an argument used within a template's init method.
+ * allocate_cb: function to build the dictionary, if it wasn't found in DSM.
+ *
+ * Returns address in the dynamic shared memory segment or in backend memory.
+ */
+void *
+ts_dict_shmem_location(DictInitData *init_data,
+					   ts_dict_build_callback allocate_cb)
+{
+	TsearchDictKey key;
+	TsearchDictEntry *entry;
+	TsearchDictData *dict_data;
+	bool		found;
+	dsm_segment *seg;
+	void	   *dict;
+	Size		dict_size;
+
+	init_dict_table();
+
+	/*
+	 * Build the dictionary in backend's memory if dictid is invalid (it may
+	 * happen if the dicionary's init method was called within
+	 * verify_dictoptions()).
+	 */
+	if (!OidIsValid(init_data->dict.id))
+	{
+		dict = allocate_cb(init_data->dict_options, &dict_size);
+
+		return dict;
+	}
+
+	/* Set up key for hashtable search */
+	key.db_id = MyDatabaseId;
+	key.dict = init_data->dict;
+
+	/* Try to find an entry in the hash table */
+	entry = (TsearchDictEntry *) dshash_find(dict_table, &key, false);
+
+	if (entry)
+	{
+		seg = dsm_find_mapping(entry->dict_dsm);
+		if (!seg)
+			seg = dict_entry_attach(entry);
+		dshash_release_lock(dict_table, entry);
+
+		dict_data = (TsearchDictData *) dsm_segment_address(seg);
+		return dict_data->dict;
+	}
+
+	/* Dictionary haven't been loaded into memory yet */
+	entry = (TsearchDictEntry *) dshash_find_or_insert(dict_table, &key,
+													   &found);
+
+	if (found)
+	{
+		/*
+		 * Someone concurrently inserted a dictionary entry since the first time
+		 * we checked.
+		 */
+		seg = dict_entry_attach(entry);
+		dshash_release_lock(dict_table, entry);
+
+		dict_data = (TsearchDictData *) dsm_segment_address(seg);
+		return dict_data->dict;
+	}
+
+	/* Build the dictionary */
+	dict = allocate_cb(init_data->dict_options, &dict_size);
+
+	/* At least initialize a dictionary entry */
+	seg = dict_entry_init(&key, entry, dict, dict_size);
+	dshash_release_lock(dict_table, entry);
+
+	pfree(dict);
+
+	dict_data = (TsearchDictData *) dsm_segment_address(seg);
+	return dict_data->dict;
+}
+
+/*
+ * Release memory occupied by the dictionary.  Function unpins DSM mapping and
+ * if the dictionary is being dropped or altered unpins the DSM segment.
+ *
+ * The segment still may leak.  It may happen if some backend used the
+ * dictionary before dropping, the backend will hold its DSM segment till
+ * disconnecting or calling lookup_ts_dictionary_cache().
+ *
+ * id, xmin, xmax, tid: information to search the dictionary's DSM segment.
+ * unpin_segment: true if we need to unpin the segment in case if the dictionary
+ *				  was dropped or altered.
+ */
+void
+ts_dict_shmem_release(Oid id, TransactionId xmin, TransactionId xmax,
+					  ItemPointerData tid, bool unpin_segment)
+{
+	TsearchDictKey key;
+	TsearchDictEntry *entry;
+
+	/*
+	 * If we didn't attach to a hash table then do nothing.
+	 */
+	if (!dict_table && !unpin_segment)
+		return;
+	/*
+	 * But if we need to unpin the DSM segment to get of rid of the segment when
+	 * the last interested process disconnects we need the hash table to find
+	 * the dictionary's entry.
+	 */
+	else if (unpin_segment)
+		init_dict_table();
+
+	/* Set up key for hashtable search */
+	key.db_id = MyDatabaseId;
+	key.dict.id = id;
+	key.dict.xmin = xmin;
+	key.dict.xmax = xmax;
+	key.dict.tid = tid;
+
+	/* Try to find an entry in the hash table */
+	entry = (TsearchDictEntry *) dshash_find(dict_table, &key, true);
+
+	if (entry)
+	{
+		dsm_segment *seg;
+
+		seg = dsm_find_mapping(entry->dict_dsm);
+
+		if (seg)
+		{
+			TsearchDictData *dict_data;
+
+			dsm_unpin_mapping(seg);
+			/*
+			 * Cancel cleanup callback to avoid a deadlock.  Cleanup is done
+			 * below.
+			 */
+			dict_data = (TsearchDictData *) dsm_segment_address(seg);
+			cancel_on_dsm_detach(seg, dict_entry_on_detach,
+								 PointerGetDatum(&dict_data->dict_key));
+			dsm_detach(seg);
+
+			entry->refcnt--;
+		}
+
+		if (unpin_segment && entry->segment_ispinned)
+		{
+			dsm_unpin_segment(entry->dict_dsm);
+			entry->segment_ispinned = false;
+
+			Assert(entry->refcnt > 0);
+			entry->refcnt--;
+		}
+
+		if (entry->refcnt == 0)
+			dshash_delete_entry(dict_table, entry);
+		else
+			dshash_release_lock(dict_table, entry);
+	}
+}
+
+/*
+ * Allocate and initialize tsearch-related shared memory.
+ */
+void
+TsearchShmemInit(void)
+{
+	bool		found;
+
+	tsearch_ctl = (TsearchCtlData *)
+		ShmemInitStruct("Full Text Search Ctl", sizeof(TsearchCtlData), &found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_TSEARCH_DSA, "tsearch_dsa");
+		LWLockRegisterTranche(LWTRANCHE_TSEARCH_TABLE, "tsearch_table");
+
+		LWLockInitialize(&tsearch_ctl->lock, LWTRANCHE_TSEARCH_DSA);
+
+		tsearch_ctl->area = DSM_HANDLE_INVALID;
+		tsearch_ctl->dict_table_handle = InvalidDsaPointer;
+	}
+}
+
+/*
+ * Report shared memory space needed by TsearchShmemInit.
+ */
+Size
+TsearchShmemSize(void)
+{
+	Size		size = 0;
+
+	/* size of service structure */
+	size = add_size(size, MAXALIGN(sizeof(TsearchCtlData)));
+
+	return size;
+}
+
+/*
+ * A comparator function for TsearchDictKey.
+ *
+ * Returns 1 if keys are equal.
+ */
+static int
+tsearch_dict_cmp(const void *a, const void *b, size_t size, void *arg)
+{
+	TsearchDictKey *k1 = (TsearchDictKey *) a;
+	TsearchDictKey *k2 = (TsearchDictKey *) b;
+
+	if (k1->db_id == k2->db_id && k1->dict.id == k2->dict.id &&
+		k1->dict.xmin == k2->dict.xmin && k1->dict.xmax == k2->dict.xmax &&
+		ItemPointerEquals(&k1->dict.tid, &k2->dict.tid))
+		return 0;
+	else
+		return 1;
+}
+
+/*
+ * A hash function for TsearchDictKey.
+ */
+static uint32
+tsearch_dict_hash(const void *a, size_t size, void *arg)
+{
+	TsearchDictKey *k = (TsearchDictKey *) a;
+	uint32		s;
+
+	s = hash_combine(0, hash_uint32(k->db_id));
+	s = hash_combine(s, hash_uint32(k->dict.id));
+	s = hash_combine(s, hash_uint32(k->dict.xmin));
+	s = hash_combine(s, hash_uint32(k->dict.xmax));
+	s = hash_combine(s,
+					 hash_uint32(BlockIdGetBlockNumber(&k->dict.tid.ip_blkid)));
+	s = hash_combine(s, hash_uint32(k->dict.tid.ip_posid));
+
+	return s;
+}
+
+/*
+ * Initialize hash table located in DSM.
+ *
+ * The hash table should be created and initialized if it doesn't exist yet.
+ */
+static void
+init_dict_table(void)
+{
+	MemoryContext old_context;
+	dsa_area   *dsa;
+
+	/* Exit if hash table was initialized alread */
+	if (dict_table)
+		return;
+
+	old_context = MemoryContextSwitchTo(TopMemoryContext);
+
+recheck_table:
+	LWLockAcquire(&tsearch_ctl->lock, LW_SHARED);
+
+	/* Hash table have been created already by someone */
+	if (DsaPointerIsValid(tsearch_ctl->dict_table_handle))
+	{
+		Assert(tsearch_ctl->area != DSM_HANDLE_INVALID);
+
+		dsa = dsa_attach(tsearch_ctl->area);
+
+		dict_table = dshash_attach(dsa,
+								   &dict_table_params,
+								   tsearch_ctl->dict_table_handle,
+								   NULL);
+	}
+	else
+	{
+		/* Try to get exclusive lock */
+		LWLockRelease(&tsearch_ctl->lock);
+		if (!LWLockAcquireOrWait(&tsearch_ctl->lock, LW_EXCLUSIVE))
+		{
+			/*
+			 * The lock was released by another backend and other backend
+			 * has concurrently created the hash table already.
+			 */
+			goto recheck_table;
+		}
+
+		dsa = dsa_create(LWTRANCHE_TSEARCH_DSA);
+		tsearch_ctl->area = dsa_get_handle(dsa);
+
+		dict_table = dshash_create(dsa, &dict_table_params, NULL);
+		tsearch_ctl->dict_table_handle = dshash_get_hash_table_handle(dict_table);
+
+		/* Remain attached until end of postmaster */
+		dsa_pin(dsa);
+	}
+
+	LWLockRelease(&tsearch_ctl->lock);
+
+	/* Remain attached until end of session */
+	dsa_pin_mapping(dsa);
+
+	MemoryContextSwitchTo(old_context);
+}
+
+/*
+ * Initialize a dictionary's DSM segment entry within shared hash table.
+ */
+static dsm_segment *
+dict_entry_init(TsearchDictKey *key, TsearchDictEntry *entry, void *dict,
+				Size dict_size)
+{
+	TsearchDictData *dict_data;
+	dsm_segment *seg;
+
+	/* Allocate a DSM segment for the compiled dictionary */
+	seg = dsm_create(TsearchDictDataHdrSize + dict_size, 0);
+	dict_data = (TsearchDictData *) dsm_segment_address(seg);
+	dict_data->dict_key = *key;
+	memcpy(dict_data->dict, dict, dict_size);
+
+	entry->key = *key;
+	entry->dict_dsm = dsm_segment_handle(seg);
+	entry->segment_ispinned = true;
+	SpinLockInit(&entry->mutex);
+	entry->refcnt = 2; /* 1 for session + 1 for postmaster */
+
+	/* Remain attached until end of postmaster */
+	dsm_pin_segment(seg);
+	/* Remain attached until end of session */
+	dsm_pin_mapping(seg);
+
+	/* Register the shared hash table cleanup callback */
+	on_dsm_detach(seg, dict_entry_on_detach,
+				  PointerGetDatum(&dict_data->dict_key));
+
+	return seg;
+}
+
+/*
+ * Attach a dictionary's DSM segment and pin mapping until end of session.
+ *
+ * Entry's reference counter increments to properly release it if no one else
+ * has mapping to this DSM using on-dsm-detach callback.
+ */
+static dsm_segment *
+dict_entry_attach(TsearchDictEntry *entry)
+{
+	dsm_segment *seg;
+	TsearchDictData *dict_data;
+
+	seg = dsm_attach(entry->dict_dsm);
+	if (seg == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("could not map dynamic shared memory segment")));
+	/* Remain attached until end of session */
+	dsm_pin_mapping(seg);
+
+	/* We need a mutex here since the entry might be locked non-exclusively */
+	SpinLockAcquire(&entry->mutex);
+	entry->refcnt++;
+	SpinLockRelease(&entry->mutex);
+
+	dict_data = (TsearchDictData *) dsm_segment_address(seg);
+	/* Register the shared hash table cleanup callback */
+	on_dsm_detach(seg, dict_entry_on_detach,
+				  PointerGetDatum(&dict_data->dict_key));
+
+	return seg;
+}
+
+/*
+ * When a session detaches from a DSM segment we need to check is someone else
+ * attached the segment. If it is not then delete the related shared hash table
+ * entry.
+ */
+static void
+dict_entry_on_detach(dsm_segment *segment, Datum datum)
+{
+	TsearchDictKey *key = (TsearchDictKey *) DatumGetPointer(datum);
+	TsearchDictEntry *entry;
+
+	/* Find the entry and lock it to decrement the refcnt */
+	entry = (TsearchDictEntry *) dshash_find(dict_table, key, true);
+	if (entry)
+	{
+		Assert(entry->refcnt > 0);
+		if (--entry->refcnt == 0)
+			dshash_delete_entry(dict_table, entry);
+		else
+			dshash_release_lock(dict_table, entry);
+	}
+}
diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c
index 8bc8d82c76..ea8fb9a039 100644
--- a/src/backend/utils/cache/ts_cache.c
+++ b/src/backend/utils/cache/ts_cache.c
@@ -40,6 +40,7 @@
 #include "commands/defrem.h"
 #include "tsearch/ts_cache.h"
 #include "tsearch/ts_public.h"
+#include "tsearch/ts_shared.h"
 #include "utils/builtins.h"
 #include "utils/catcache.h"
 #include "utils/fmgroids.h"
@@ -75,6 +76,7 @@ static TSConfigCacheEntry *lastUsedConfig = NULL;
 char	   *TSCurrentConfig = NULL;
 
 static Oid	TSCurrentConfigCache = InvalidOid;
+static bool has_invalid_dictionary = false;
 
 
 /*
@@ -86,6 +88,10 @@ static Oid	TSCurrentConfigCache = InvalidOid;
  * doesn't seem worth the trouble to determine that; we just flush all the
  * entries of the related hash table.
  *
+ * We set has_invalid_dictionary to true to unpin all used segments later on
+ * a first text search function usage.  It isn't safe to call
+ * ts_dict_shmem_release() here since it may call kernel functions.
+ *
  * We can use the same function for all TS caches by passing the hash
  * table address as the "arg".
  */
@@ -98,13 +104,48 @@ InvalidateTSCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
 
 	hash_seq_init(&status, hash);
 	while ((entry = (TSAnyCacheEntry *) hash_seq_search(&status)) != NULL)
+	{
+		if (cacheid == TSDICTOID)
+		{
+			TSDictionaryCacheEntry *dict_entry;
+
+			dict_entry = (TSDictionaryCacheEntry *) entry;
+			if (dict_entry->hashvalue == hashvalue)
+			{
+				dict_entry->shmem_valid = false;
+				has_invalid_dictionary = true;
+			}
+		}
+
 		entry->isvalid = false;
+	}
 
 	/* Also invalidate the current-config cache if it's pg_ts_config */
 	if (hash == TSConfigCacheHash)
 		TSCurrentConfigCache = InvalidOid;
 }
 
+/*
+ * Unpin shared segments of all invalid dictionary entries.
+ */
+static void
+do_ts_dict_shmem_release(void)
+{
+	HASH_SEQ_STATUS status;
+	TSDictionaryCacheEntry *entry;
+
+	if (!has_invalid_dictionary)
+		return;
+
+	hash_seq_init(&status, TSDictionaryCacheHash);
+	while ((entry = (TSDictionaryCacheEntry *) hash_seq_search(&status)) != NULL)
+		if (!entry->shmem_valid)
+			ts_dict_shmem_release(entry->dictId, entry->dict_xmin,
+								  entry->dict_xmax, entry->dict_tid, false);
+
+	has_invalid_dictionary = false;
+}
+
 /*
  * Fetch parser cache entry
  */
@@ -253,6 +294,13 @@ lookup_ts_dictionary_cache(Oid dictId)
 		Form_pg_ts_template template;
 		MemoryContext saveCtx;
 
+		/*
+		 * It is possible that some invalid entries hold a DSM mapping and we
+		 * need to unpin it to avoid memory leaking.  We will unpin segments of
+		 * all other invalid dictionaries.
+		 */
+		do_ts_dict_shmem_release();
+
 		tpdict = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
 		if (!HeapTupleIsValid(tpdict))
 			elog(ERROR, "cache lookup failed for text search dictionary %u",
@@ -359,6 +407,9 @@ lookup_ts_dictionary_cache(Oid dictId)
 		fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx);
 
 		entry->isvalid = true;
+		entry->hashvalue =
+			GetSysCacheHashValue1(TSDICTOID, ObjectIdGetDatum(entry->dictId));
+		entry->shmem_valid = true;
 	}
 
 	lastUsedDictionary = entry;
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 96c7732006..49a3319a11 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -219,6 +219,8 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TUPLESTORE,
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
+	LWTRANCHE_TSEARCH_DSA,
+	LWTRANCHE_TSEARCH_TABLE,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/tsearch/ts_cache.h b/src/include/tsearch/ts_cache.h
index 2298e0a275..14e13bf252 100644
--- a/src/include/tsearch/ts_cache.h
+++ b/src/include/tsearch/ts_cache.h
@@ -54,6 +54,9 @@ typedef struct TSDictionaryCacheEntry
 	Oid			dictId;
 	bool		isvalid;
 
+	uint32		hashvalue;	/* hash value of the dictionary's OID */
+	bool		shmem_valid;
+
 	TransactionId	dict_xmin;	/* XMIN of the dictionary's tuple */
 	TransactionId	dict_xmax;	/* XMAX of the dictionary's tuple */
 	ItemPointerData	dict_tid;	/* TID of the dictionary's tuple */
diff --git a/src/include/tsearch/ts_shared.h b/src/include/tsearch/ts_shared.h
new file mode 100644
index 0000000000..1e506ef737
--- /dev/null
+++ b/src/include/tsearch/ts_shared.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * ts_shared.h
+ *	  tsearch shared memory management
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ *
+ * src/include/tsearch/ts_shared.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef TS_SHARED_H
+#define TS_SHARED_H
+
+#include "tsearch/ts_public.h"
+
+typedef void *(*ts_dict_build_callback) (List *dictoptions, Size *size);
+
+extern void *ts_dict_shmem_location(DictInitData *init_data,
+									ts_dict_build_callback allocate_cb);
+extern void ts_dict_shmem_release(Oid id, TransactionId xmin,
+								  TransactionId xmax, ItemPointerData tid,
+								  bool unpin_segment);
+
+extern void TsearchShmemInit(void);
+extern Size TsearchShmemSize(void);
+
+#endif							/* TS_SHARED_H */
-- 
2.20.1

