1/*-------------------------------------------------------------------------
2 *
3 * session.c
4 * Encapsulation of user session.
5 *
6 * This is intended to contain data that needs to be shared between backends
7 * performing work for a client session. In particular such a session is
8 * shared between the leader and worker processes for parallel queries. At
9 * some later point it might also become useful infrastructure for separating
10 * backends from client connections, e.g. for the purpose of pooling.
11 *
12 * Currently this infrastructure is used to share:
13 * - typemod registry for ephemeral row-types, i.e. BlessTupleDesc etc.
14 *
15 * Portions Copyright (c) 2017-2019, PostgreSQL Global Development Group
16 *
17 * src/backend/access/common/session.c
18 *
19 *-------------------------------------------------------------------------
20 */
21#include "postgres.h"
22
23#include "access/session.h"
24#include "storage/lwlock.h"
25#include "storage/shm_toc.h"
26#include "utils/memutils.h"
27#include "utils/typcache.h"
28
29/* Magic number for per-session DSM TOC. */
30#define SESSION_MAGIC 0xabb0fbc9
31
32/*
33 * We want to create a DSA area to store shared state that has the same
34 * lifetime as a session. So far, it's only used to hold the shared record
35 * type registry. We don't want it to have to create any DSM segments just
36 * yet in common cases, so we'll give it enough space to hold a very small
37 * SharedRecordTypmodRegistry.
38 */
39#define SESSION_DSA_SIZE 0x30000
40
41/*
42 * Magic numbers for state sharing in the per-session DSM area.
43 */
44#define SESSION_KEY_DSA UINT64CONST(0xFFFFFFFFFFFF0001)
45#define SESSION_KEY_RECORD_TYPMOD_REGISTRY UINT64CONST(0xFFFFFFFFFFFF0002)
46
47/* This backend's current session. */
48Session *CurrentSession = NULL;
49
50/*
51 * Set up CurrentSession to point to an empty Session object.
52 */
53void
54InitializeSession(void)
55{
56 CurrentSession = MemoryContextAllocZero(TopMemoryContext, sizeof(Session));
57}
58
59/*
60 * Initialize the per-session DSM segment if it isn't already initialized, and
61 * return its handle so that worker processes can attach to it.
62 *
63 * Unlike the per-context DSM segment, this segment and its contents are
64 * reused for future parallel queries.
65 *
66 * Return DSM_HANDLE_INVALID if a segment can't be allocated due to lack of
67 * resources.
68 */
69dsm_handle
70GetSessionDsmHandle(void)
71{
72 shm_toc_estimator estimator;
73 shm_toc *toc;
74 dsm_segment *seg;
75 size_t typmod_registry_size;
76 size_t size;
77 void *dsa_space;
78 void *typmod_registry_space;
79 dsa_area *dsa;
80 MemoryContext old_context;
81
82 /*
83 * If we have already created a session-scope DSM segment in this backend,
84 * return its handle. The same segment will be used for the rest of this
85 * backend's lifetime.
86 */
87 if (CurrentSession->segment != NULL)
88 return dsm_segment_handle(CurrentSession->segment);
89
90 /* Otherwise, prepare to set one up. */
91 old_context = MemoryContextSwitchTo(TopMemoryContext);
92 shm_toc_initialize_estimator(&estimator);
93
94 /* Estimate space for the per-session DSA area. */
95 shm_toc_estimate_keys(&estimator, 1);
96 shm_toc_estimate_chunk(&estimator, SESSION_DSA_SIZE);
97
98 /* Estimate space for the per-session record typmod registry. */
99 typmod_registry_size = SharedRecordTypmodRegistryEstimate();
100 shm_toc_estimate_keys(&estimator, 1);
101 shm_toc_estimate_chunk(&estimator, typmod_registry_size);
102
103 /* Set up segment and TOC. */
104 size = shm_toc_estimate(&estimator);
105 seg = dsm_create(size, DSM_CREATE_NULL_IF_MAXSEGMENTS);
106 if (seg == NULL)
107 {
108 MemoryContextSwitchTo(old_context);
109
110 return DSM_HANDLE_INVALID;
111 }
112 toc = shm_toc_create(SESSION_MAGIC,
113 dsm_segment_address(seg),
114 size);
115
116 /* Create per-session DSA area. */
117 dsa_space = shm_toc_allocate(toc, SESSION_DSA_SIZE);
118 dsa = dsa_create_in_place(dsa_space,
119 SESSION_DSA_SIZE,
120 LWTRANCHE_SESSION_DSA,
121 seg);
122 shm_toc_insert(toc, SESSION_KEY_DSA, dsa_space);
123
124
125 /* Create session-scoped shared record typmod registry. */
126 typmod_registry_space = shm_toc_allocate(toc, typmod_registry_size);
127 SharedRecordTypmodRegistryInit((SharedRecordTypmodRegistry *)
128 typmod_registry_space, seg, dsa);
129 shm_toc_insert(toc, SESSION_KEY_RECORD_TYPMOD_REGISTRY,
130 typmod_registry_space);
131
132 /*
133 * If we got this far, we can pin the shared memory so it stays mapped for
134 * the rest of this backend's life. If we don't make it this far, cleanup
135 * callbacks for anything we installed above (ie currently
136 * SharedRecordTypmodRegistry) will run when the DSM segment is detached
137 * by CurrentResourceOwner so we aren't left with a broken CurrentSession.
138 */
139 dsm_pin_mapping(seg);
140 dsa_pin_mapping(dsa);
141
142 /* Make segment and area available via CurrentSession. */
143 CurrentSession->segment = seg;
144 CurrentSession->area = dsa;
145
146 MemoryContextSwitchTo(old_context);
147
148 return dsm_segment_handle(seg);
149}
150
151/*
152 * Attach to a per-session DSM segment provided by a parallel leader.
153 */
154void
155AttachSession(dsm_handle handle)
156{
157 dsm_segment *seg;
158 shm_toc *toc;
159 void *dsa_space;
160 void *typmod_registry_space;
161 dsa_area *dsa;
162 MemoryContext old_context;
163
164 old_context = MemoryContextSwitchTo(TopMemoryContext);
165
166 /* Attach to the DSM segment. */
167 seg = dsm_attach(handle);
168 if (seg == NULL)
169 elog(ERROR, "could not attach to per-session DSM segment");
170 toc = shm_toc_attach(SESSION_MAGIC, dsm_segment_address(seg));
171
172 /* Attach to the DSA area. */
173 dsa_space = shm_toc_lookup(toc, SESSION_KEY_DSA, false);
174 dsa = dsa_attach_in_place(dsa_space, seg);
175
176 /* Make them available via the current session. */
177 CurrentSession->segment = seg;
178 CurrentSession->area = dsa;
179
180 /* Attach to the shared record typmod registry. */
181 typmod_registry_space =
182 shm_toc_lookup(toc, SESSION_KEY_RECORD_TYPMOD_REGISTRY, false);
183 SharedRecordTypmodRegistryAttach((SharedRecordTypmodRegistry *)
184 typmod_registry_space);
185
186 /* Remain attached until end of backend or DetachSession(). */
187 dsm_pin_mapping(seg);
188 dsa_pin_mapping(dsa);
189
190 MemoryContextSwitchTo(old_context);
191}
192
193/*
194 * Detach from the current session DSM segment. It's not strictly necessary
195 * to do this explicitly since we'll detach automatically at backend exit, but
196 * if we ever reuse parallel workers it will become important for workers to
197 * detach from one session before attaching to another. Note that this runs
198 * detach hooks.
199 */
200void
201DetachSession(void)
202{
203 /* Runs detach hooks. */
204 dsm_detach(CurrentSession->segment);
205 CurrentSession->segment = NULL;
206 dsa_detach(CurrentSession->area);
207 CurrentSession->area = NULL;
208}
209