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-2018, 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. */
48 Session    *CurrentSession = NULL;
49 
50 /*
51  * Set up CurrentSession to point to an empty Session object.
52  */
53 void
InitializeSession(void)54 InitializeSession(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  */
69 dsm_handle
GetSessionDsmHandle(void)70 GetSessionDsmHandle(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 	 * SharedRecordTypemodRegistry) 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  */
154 void
AttachSession(dsm_handle handle)155 AttachSession(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  */
200 void
DetachSession(void)201 DetachSession(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