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. */ |
48 | Session *CurrentSession = NULL; |
49 | |
50 | /* |
51 | * Set up CurrentSession to point to an empty Session object. |
52 | */ |
53 | 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 |
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 | * 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 | */ |
154 | void |
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 |
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 | |