| 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 | |