diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e4a01699e4..858423354e 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1355,6 +1355,36 @@ include_dir 'conf.d'
+
+ shared_dictionaries (integer)
+
+ shared_dictionaries configuration parameter
+
+
+
+
+ Sets the maximum number of text search dictionaries loaded into shared
+ memory. The default is 10 dictionaries.
+
+
+
+ Currently controls only loading of Ispell
+ dictionaries (see ).
+ After compiling the dictionary it will be copied into shared memory.
+ Another backends on first use of the dictionary will use it from shared
+ memory, so it doesn't need to compile the dictionary second time.
+ DictFile and AffFile are used to
+ search the dictionary in shared memory.
+
+
+
+ If the number of simultaneously loaded dictionaries reaches the maximum
+ allowed number then a new dictionary will be loaded into local memory of
+ a backend.
+
+
+
+
huge_pages (enum)
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0c86a581c0..c7dce8cac5 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/backend_random.h"
#include "utils/snapmgr.h"
@@ -150,6 +151,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
size = add_size(size, SyncScanShmemSize());
size = add_size(size, AsyncShmemSize());
size = add_size(size, BackendRandomShmemSize());
+ size = add_size(size, TsearchShmemSize());
#ifdef EXEC_BACKEND
size = add_size(size, ShmemBackendArraySize());
#endif
@@ -271,6 +273,11 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
AsyncShmemInit();
BackendRandomShmemInit();
+ /*
+ * Set up shared memory to tsearch
+ */
+ TsearchShmemInit();
+
#ifdef EXEC_BACKEND
/*
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 71caac1a1f..2446db7266 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -520,6 +520,7 @@ RegisterLWLockTranches(void)
"shared_tuplestore");
LWLockRegisterTranche(LWTRANCHE_TBM, "tbm");
LWLockRegisterTranche(LWTRANCHE_PARALLEL_APPEND, "parallel_append");
+ LWLockRegisterTranche(LWTRANCHE_TSEARCH_DSA, "tsearch_dsa");
/* Register named tranches. */
for (i = 0; i < NamedLWLockTrancheRequests; i++)
diff --git a/src/backend/tsearch/Makefile b/src/backend/tsearch/Makefile
index 227468ae9e..860cd196e9 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..4682eab506
--- /dev/null
+++ b/src/backend/tsearch/ts_shared.c
@@ -0,0 +1,179 @@
+/*-------------------------------------------------------------------------
+ *
+ * ts_shared.c
+ * tsearch shared memory management
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/tsearch/ts_shared.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "tsearch/ts_shared.h"
+
+
+/*
+ * Hash table structures
+ */
+
+typedef struct
+{
+ char dictfile[MAXPGPATH];
+ char afffile[MAXPGPATH];
+} TsearchDictKey;
+
+typedef struct
+{
+ TsearchDictKey key;
+ dsm_handle dict_dsm;
+} TsearchDictEntry;
+
+static HTAB *dict_table;
+
+/*
+ * Shared struct for locking
+ */
+typedef struct
+{
+ LWLock lock;
+} TsearchCtlData;
+
+static TsearchCtlData *tsearch_ctl;
+
+/*
+ * GUC variable for maximum number of shared dictionaries
+ */
+int shared_dictionaries = 10;
+
+/*
+ * Return handle to a dynamic shared memory using hash table. If shared memory
+ * for dictfile and afffile doesn't allocated yet, do it.
+ *
+ * dictbuild: building structure for the dictionary.
+ * dictfile: .dict file of the dictionary.
+ * afffile: .aff file of the dictionary.
+ * allocate_cb: function to build the dictionary, if it wasn't found in DSM.
+ */
+dsm_handle
+ispell_shmem_location(void *dictbuild,
+ const char *dictfile, const char *afffile,
+ ispell_build_callback allocate_cb)
+{
+ TsearchDictKey key;
+ TsearchDictEntry *entry;
+ bool found;
+ dsm_segment *seg;
+ dsm_handle res;
+
+ StrNCpy(key.dictfile, dictfile, MAXPGPATH);
+ StrNCpy(key.afffile, afffile, MAXPGPATH);
+
+refind_entry:
+ LWLockAcquire(&tsearch_ctl->lock, LW_SHARED);
+
+ entry = (TsearchDictEntry *) hash_search(dict_table, &key, HASH_FIND,
+ &found);
+
+ /* Dictionary wasn't load into memory */
+ if (!found)
+ {
+ void *ispell_dict,
+ *dict_location;
+ Size ispell_size;
+
+ /* Try to get exclusive lock */
+ LWLockRelease(&tsearch_ctl->lock);
+ if (!LWLockAcquireOrWait(&tsearch_ctl->lock, LW_EXCLUSIVE))
+ {
+ /*
+ * The lock was released by another backend, try to refind an entry.
+ */
+ goto refind_entry;
+ }
+
+ entry = (TsearchDictEntry *) hash_search(dict_table, &key,
+ HASH_ENTER_NULL,
+ &found);
+
+ /*
+ * There is no space in shared hash table, let backend to build the
+ * dictionary within its memory context.
+ */
+ if (entry == NULL)
+ return DSM_HANDLE_INVALID;
+
+ /* The lock was free so add new entry */
+ ispell_dict = allocate_cb(dictbuild, dictfile, afffile, &ispell_size);
+
+ seg = dsm_create(ispell_size, 0);
+ dict_location = dsm_segment_address(seg);
+ memcpy(dict_location, ispell_dict, ispell_size);
+
+ pfree(ispell_dict);
+
+ entry->dict_dsm = dsm_segment_handle(seg);
+ res = entry->dict_dsm;
+
+ /* Remain attached until end of postmaster */
+ dsm_pin_segment(seg);
+
+ dsm_detach(seg);
+ }
+ else
+ {
+ res = entry->dict_dsm;
+ }
+
+ LWLockRelease(&tsearch_ctl->lock);
+
+ return res;
+}
+
+/*
+ * Allocate and initialize tsearch-related shared memory.
+ */
+void
+TsearchShmemInit(void)
+{
+ HASHCTL ctl;
+ bool found;
+
+ tsearch_ctl = (TsearchCtlData *)
+ ShmemInitStruct("Full Text Search Ctl", sizeof(TsearchCtlData), &found);
+
+ if (!found)
+ LWLockInitialize(&tsearch_ctl->lock, LWTRANCHE_TSEARCH_DSA);
+
+ memset(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(TsearchDictKey);
+ ctl.entrysize = sizeof(TsearchDictEntry);
+
+ dict_table = ShmemInitHash("Shared Tsearch Lookup Table",
+ shared_dictionaries, shared_dictionaries,
+ &ctl,
+ HASH_ELEM | HASH_BLOBS);
+}
+
+/*
+ * Report shared memory space needed by TsearchShmemInit.
+ */
+Size
+TsearchShmemSize(void)
+{
+ Size size = 0;
+
+ /* size of service structure */
+ size = add_size(size, MAXALIGN(sizeof(TsearchCtlData)));
+
+ /* size of lookup hash table */
+ size = add_size(size, hash_estimate_size(shared_dictionaries,
+ sizeof(TsearchDictEntry)));
+
+ return size;
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 72f6be329e..dbc9bf93f0 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -76,6 +76,7 @@
#include "storage/predicate.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
+#include "tsearch/ts_shared.h"
#include "utils/builtins.h"
#include "utils/bytea.h"
#include "utils/guc_tables.h"
@@ -2910,6 +2911,19 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"shared_dictionaries", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the maximum number of text search dictionaries loaded into shared memory."),
+ gettext_noop("Currently controls only loading of Ispell dictionaries. "
+ "If the number of simultaneously loaded dictionaries "
+ "reaches the maximum allowed number then a new dictionary "
+ "will be loaded into local memory of a backend.")
+ },
+ &shared_dictionaries,
+ 10, 0, INT_MAX,
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 69f40f04b0..b83ffe6a39 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -131,6 +131,7 @@
# mmap
# use none to disable dynamic shared memory
# (change requires restart)
+#shared_dictionaries = 10 # (change requires restart)
# - Disk -
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index c21bfe2f66..2bb80cdd26 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -219,6 +219,7 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_SHARED_TUPLESTORE,
LWTRANCHE_TBM,
LWTRANCHE_PARALLEL_APPEND,
+ LWTRANCHE_TSEARCH_DSA,
LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
diff --git a/src/include/tsearch/ts_shared.h b/src/include/tsearch/ts_shared.h
new file mode 100644
index 0000000000..4bcfb437ef
--- /dev/null
+++ b/src/include/tsearch/ts_shared.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * ts_shared.h
+ * tsearch shared memory management
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ *
+ * src/include/tsearch/ts_shared.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef TS_SHARED_H
+#define TS_SHARED_H
+
+#include "c.h"
+#include "storage/dsm.h"
+
+/*
+ * GUC variable for maximum number of shared dictionaries
+ */
+extern int shared_dictionaries;
+
+typedef void *(*ispell_build_callback) (void *dictbuild,
+ const char *dictfile,
+ const char *afffile,
+ Size *size);
+
+extern dsm_handle ispell_shmem_location(void *dictbuild,
+ const char *dictfile, const char *afffile,
+ ispell_build_callback allocate_cb);
+
+extern void TsearchShmemInit(void);
+extern Size TsearchShmemSize(void);
+
+#endif /* TS_SHARED_H */