/src/hdf5/src/H5EAdblock.c
Line | Count | Source |
1 | | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
2 | | * Copyright by The HDF Group. * |
3 | | * All rights reserved. * |
4 | | * * |
5 | | * This file is part of HDF5. The full HDF5 copyright notice, including * |
6 | | * terms governing use, modification, and redistribution, is contained in * |
7 | | * the LICENSE file, which can be found at the root of the source code * |
8 | | * distribution tree, or in https://www.hdfgroup.org/licenses. * |
9 | | * If you do not have access to either file, you may request a copy from * |
10 | | * help@hdfgroup.org. * |
11 | | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
12 | | |
13 | | /*------------------------------------------------------------------------- |
14 | | * |
15 | | * Created: H5EAdblock.c |
16 | | * |
17 | | * Purpose: Data block routines for extensible arrays. |
18 | | * |
19 | | *------------------------------------------------------------------------- |
20 | | */ |
21 | | |
22 | | /**********************/ |
23 | | /* Module Declaration */ |
24 | | /**********************/ |
25 | | |
26 | | #include "H5EAmodule.h" /* This source code file is part of the H5EA module */ |
27 | | |
28 | | /***********************/ |
29 | | /* Other Packages Used */ |
30 | | /***********************/ |
31 | | |
32 | | /***********/ |
33 | | /* Headers */ |
34 | | /***********/ |
35 | | #include "H5private.h" /* Generic Functions */ |
36 | | #include "H5Eprivate.h" /* Error handling */ |
37 | | #include "H5EApkg.h" /* Extensible Arrays */ |
38 | | #include "H5FLprivate.h" /* Free Lists */ |
39 | | #include "H5MFprivate.h" /* File memory management */ |
40 | | #include "H5VMprivate.h" /* Vectors and arrays */ |
41 | | |
42 | | /****************/ |
43 | | /* Local Macros */ |
44 | | /****************/ |
45 | | |
46 | | /******************/ |
47 | | /* Local Typedefs */ |
48 | | /******************/ |
49 | | |
50 | | /********************/ |
51 | | /* Package Typedefs */ |
52 | | /********************/ |
53 | | |
54 | | /********************/ |
55 | | /* Local Prototypes */ |
56 | | /********************/ |
57 | | |
58 | | /*********************/ |
59 | | /* Package Variables */ |
60 | | /*********************/ |
61 | | |
62 | | /*****************************/ |
63 | | /* Library Private Variables */ |
64 | | /*****************************/ |
65 | | |
66 | | /*******************/ |
67 | | /* Local Variables */ |
68 | | /*******************/ |
69 | | |
70 | | /* Declare a free list to manage the H5EA_dblock_t struct */ |
71 | | H5FL_DEFINE_STATIC(H5EA_dblock_t); |
72 | | |
73 | | /*------------------------------------------------------------------------- |
74 | | * Function: H5EA__dblock_alloc |
75 | | * |
76 | | * Purpose: Allocate extensible array data block |
77 | | * |
78 | | * Return: Non-NULL pointer to data block on success/NULL on failure |
79 | | * |
80 | | *------------------------------------------------------------------------- |
81 | | */ |
82 | | H5EA_dblock_t * |
83 | | H5EA__dblock_alloc(H5EA_hdr_t *hdr, void *parent, size_t nelmts) |
84 | 0 | { |
85 | 0 | H5EA_dblock_t *dblock = NULL; /* Extensible array data block */ |
86 | 0 | H5EA_dblock_t *ret_value = NULL; |
87 | |
|
88 | 0 | FUNC_ENTER_PACKAGE |
89 | | |
90 | | /* Check arguments */ |
91 | 0 | assert(hdr); |
92 | 0 | assert(parent); |
93 | 0 | assert(nelmts > 0); |
94 | | |
95 | | /* Allocate memory for the data block */ |
96 | 0 | if (NULL == (dblock = H5FL_CALLOC(H5EA_dblock_t))) |
97 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTALLOC, NULL, |
98 | 0 | "memory allocation failed for extensible array data block"); |
99 | | |
100 | | /* Share common array information */ |
101 | 0 | if (H5EA__hdr_incr(hdr) < 0) |
102 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTINC, NULL, "can't increment reference count on shared array header"); |
103 | 0 | dblock->hdr = hdr; |
104 | | |
105 | | /* Set non-zero internal fields */ |
106 | 0 | dblock->parent = parent; |
107 | 0 | dblock->nelmts = nelmts; |
108 | | |
109 | | /* Check if the data block is not going to be paged */ |
110 | 0 | if (nelmts > hdr->dblk_page_nelmts) { |
111 | | /* Set the # of pages in the direct block */ |
112 | 0 | dblock->npages = nelmts / hdr->dblk_page_nelmts; |
113 | 0 | assert(nelmts == (dblock->npages * hdr->dblk_page_nelmts)); |
114 | 0 | } /* end if */ |
115 | 0 | else { |
116 | | /* Allocate buffer for elements in data block */ |
117 | 0 | if (NULL == (dblock->elmts = H5EA__hdr_alloc_elmts(hdr, nelmts))) |
118 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTALLOC, NULL, |
119 | 0 | "memory allocation failed for data block element buffer"); |
120 | 0 | } /* end else */ |
121 | | |
122 | | /* Set the return value */ |
123 | 0 | ret_value = dblock; |
124 | |
|
125 | 0 | done: |
126 | 0 | if (!ret_value) |
127 | 0 | if (dblock && H5EA__dblock_dest(dblock) < 0) |
128 | 0 | HDONE_ERROR(H5E_EARRAY, H5E_CANTFREE, NULL, "unable to destroy extensible array data block"); |
129 | |
|
130 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
131 | 0 | } /* end H5EA__dblock_alloc() */ |
132 | | |
133 | | /*------------------------------------------------------------------------- |
134 | | * Function: H5EA__dblock_create |
135 | | * |
136 | | * Purpose: Creates a new extensible array data block in the file |
137 | | * |
138 | | * Return: Valid file address on success/HADDR_UNDEF on failure |
139 | | * |
140 | | *------------------------------------------------------------------------- |
141 | | */ |
142 | | haddr_t |
143 | | H5EA__dblock_create(H5EA_hdr_t *hdr, void *parent, bool *stats_changed, hsize_t dblk_off, size_t nelmts) |
144 | 0 | { |
145 | 0 | H5EA_dblock_t *dblock = NULL; /* Extensible array data block */ |
146 | 0 | haddr_t dblock_addr; /* Extensible array data block address */ |
147 | 0 | bool inserted = false; /* Whether the header was inserted into cache */ |
148 | 0 | haddr_t ret_value = HADDR_UNDEF; |
149 | |
|
150 | 0 | FUNC_ENTER_PACKAGE |
151 | | |
152 | | /* Sanity check */ |
153 | 0 | assert(hdr); |
154 | 0 | assert(stats_changed); |
155 | 0 | assert(nelmts > 0); |
156 | | |
157 | | /* Allocate the data block */ |
158 | 0 | if (NULL == (dblock = H5EA__dblock_alloc(hdr, parent, nelmts))) |
159 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTALLOC, HADDR_UNDEF, |
160 | 0 | "memory allocation failed for extensible array data block"); |
161 | | |
162 | | /* Set size of data block on disk */ |
163 | 0 | dblock->size = H5EA_DBLOCK_SIZE(dblock); |
164 | | |
165 | | /* Set offset of block in array's address space */ |
166 | 0 | dblock->block_off = dblk_off; |
167 | | |
168 | | /* Allocate space for the data block on disk */ |
169 | 0 | if (HADDR_UNDEF == (dblock_addr = H5MF_alloc(hdr->f, H5FD_MEM_EARRAY_DBLOCK, (hsize_t)dblock->size))) |
170 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTALLOC, HADDR_UNDEF, |
171 | 0 | "file allocation failed for extensible array data block"); |
172 | 0 | dblock->addr = dblock_addr; |
173 | | |
174 | | /* Don't initialize elements if paged */ |
175 | 0 | if (!dblock->npages) |
176 | | /* Clear any elements in data block to fill value */ |
177 | 0 | if ((hdr->cparam.cls->fill)(dblock->elmts, (size_t)dblock->nelmts) < 0) |
178 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTSET, HADDR_UNDEF, |
179 | 0 | "can't set extensible array data block elements to class's fill value"); |
180 | | |
181 | | /* Cache the new extensible array data block */ |
182 | 0 | if (H5AC_insert_entry(hdr->f, H5AC_EARRAY_DBLOCK, dblock_addr, dblock, H5AC__NO_FLAGS_SET) < 0) |
183 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTINSERT, HADDR_UNDEF, |
184 | 0 | "can't add extensible array data block to cache"); |
185 | 0 | inserted = true; |
186 | | |
187 | | /* Add data block as child of 'top' proxy */ |
188 | 0 | if (hdr->top_proxy) { |
189 | 0 | if (H5AC_proxy_entry_add_child(hdr->top_proxy, hdr->f, dblock) < 0) |
190 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTSET, HADDR_UNDEF, |
191 | 0 | "unable to add extensible array entry as child of array proxy"); |
192 | 0 | dblock->top_proxy = hdr->top_proxy; |
193 | 0 | } /* end if */ |
194 | | |
195 | | /* Update extensible array data block statistics */ |
196 | 0 | hdr->stats.stored.ndata_blks++; |
197 | 0 | hdr->stats.stored.data_blk_size += dblock->size; |
198 | | |
199 | | /* Increment count of elements "realized" */ |
200 | 0 | hdr->stats.stored.nelmts += nelmts; |
201 | | |
202 | | /* Mark the statistics as changed */ |
203 | 0 | *stats_changed = true; |
204 | | |
205 | | /* Set address of data block to return */ |
206 | 0 | ret_value = dblock_addr; |
207 | |
|
208 | 0 | done: |
209 | 0 | if (!H5_addr_defined(ret_value)) |
210 | 0 | if (dblock) { |
211 | | /* Remove from cache, if inserted */ |
212 | 0 | if (inserted) |
213 | 0 | if (H5AC_remove_entry(dblock) < 0) |
214 | 0 | HDONE_ERROR(H5E_EARRAY, H5E_CANTREMOVE, HADDR_UNDEF, |
215 | 0 | "unable to remove extensible array data block from cache"); |
216 | | |
217 | | /* Release data block's disk space */ |
218 | 0 | if (H5_addr_defined(dblock->addr) && |
219 | 0 | H5MF_xfree(hdr->f, H5FD_MEM_EARRAY_DBLOCK, dblock->addr, (hsize_t)dblock->size) < 0) |
220 | 0 | HDONE_ERROR(H5E_EARRAY, H5E_CANTFREE, HADDR_UNDEF, |
221 | 0 | "unable to release extensible array data block"); |
222 | | |
223 | | /* Destroy data block */ |
224 | 0 | if (H5EA__dblock_dest(dblock) < 0) |
225 | 0 | HDONE_ERROR(H5E_EARRAY, H5E_CANTFREE, HADDR_UNDEF, |
226 | 0 | "unable to destroy extensible array data block"); |
227 | 0 | } /* end if */ |
228 | |
|
229 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
230 | 0 | } /* end H5EA__dblock_create() */ |
231 | | |
232 | | /*------------------------------------------------------------------------- |
233 | | * Function: H5EA__dblock_sblk_idx |
234 | | * |
235 | | * Purpose: Compute the index of the super block where the element is |
236 | | * located. |
237 | | * |
238 | | * Return: Super block index on success/Can't fail |
239 | | * |
240 | | *------------------------------------------------------------------------- |
241 | | */ |
242 | | unsigned |
243 | | H5EA__dblock_sblk_idx(const H5EA_hdr_t *hdr, hsize_t idx) |
244 | 0 | { |
245 | 0 | unsigned sblk_idx = 0; /* Which superblock does this index fall in? */ |
246 | |
|
247 | 0 | FUNC_ENTER_PACKAGE_NOERR |
248 | | |
249 | | /* Sanity check */ |
250 | 0 | assert(hdr); |
251 | 0 | assert(idx >= hdr->cparam.idx_blk_elmts); |
252 | | |
253 | | /* Adjust index for elements in index block */ |
254 | 0 | idx -= hdr->cparam.idx_blk_elmts; |
255 | | |
256 | | /* Determine the superblock information for the index */ |
257 | 0 | H5_CHECK_OVERFLOW(idx, /*From:*/ hsize_t, /*To:*/ uint64_t); |
258 | 0 | sblk_idx = H5VM_log2_gen((uint64_t)((idx / hdr->cparam.data_blk_min_elmts) + 1)); |
259 | |
|
260 | 0 | FUNC_LEAVE_NOAPI(sblk_idx) |
261 | 0 | } /* end H5EA__dblock_sblk_idx() */ |
262 | | |
263 | | /*------------------------------------------------------------------------- |
264 | | * Function: H5EA__dblock_protect |
265 | | * |
266 | | * Purpose: Convenience wrapper around protecting extensible array data block |
267 | | * |
268 | | * Return: Non-NULL pointer to data block on success/NULL on failure |
269 | | * |
270 | | *------------------------------------------------------------------------- |
271 | | */ |
272 | | H5EA_dblock_t * |
273 | | H5EA__dblock_protect(H5EA_hdr_t *hdr, void *parent, haddr_t dblk_addr, size_t dblk_nelmts, unsigned flags) |
274 | 0 | { |
275 | 0 | H5EA_dblock_t *dblock; /* Extensible array data block */ |
276 | 0 | H5EA_dblock_cache_ud_t udata; /* Information needed for loading data block */ |
277 | 0 | H5EA_dblock_t *ret_value = NULL; |
278 | |
|
279 | 0 | FUNC_ENTER_PACKAGE |
280 | | |
281 | | /* Sanity check */ |
282 | 0 | assert(hdr); |
283 | 0 | assert(H5_addr_defined(dblk_addr)); |
284 | 0 | assert(dblk_nelmts); |
285 | | |
286 | | /* only the H5AC__READ_ONLY_FLAG may be set */ |
287 | 0 | assert((flags & (unsigned)(~H5AC__READ_ONLY_FLAG)) == 0); |
288 | | |
289 | | /* Set up user data */ |
290 | 0 | udata.hdr = hdr; |
291 | 0 | udata.parent = parent; |
292 | 0 | udata.nelmts = dblk_nelmts; |
293 | 0 | udata.dblk_addr = dblk_addr; |
294 | | |
295 | | /* Protect the data block */ |
296 | 0 | if (NULL == |
297 | 0 | (dblock = (H5EA_dblock_t *)H5AC_protect(hdr->f, H5AC_EARRAY_DBLOCK, dblk_addr, &udata, flags))) |
298 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTPROTECT, NULL, |
299 | 0 | "unable to protect extensible array data block, address = %llu", |
300 | 0 | (unsigned long long)dblk_addr); |
301 | | |
302 | | /* Create top proxy, if it doesn't exist */ |
303 | 0 | if (hdr->top_proxy && NULL == dblock->top_proxy) { |
304 | | /* Add data block as child of 'top' proxy */ |
305 | 0 | if (H5AC_proxy_entry_add_child(hdr->top_proxy, hdr->f, dblock) < 0) |
306 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTSET, NULL, |
307 | 0 | "unable to add extensible array entry as child of array proxy"); |
308 | 0 | dblock->top_proxy = hdr->top_proxy; |
309 | 0 | } |
310 | | |
311 | | /* Set return value */ |
312 | 0 | ret_value = dblock; |
313 | |
|
314 | 0 | done: |
315 | | |
316 | | /* Clean up on error */ |
317 | 0 | if (!ret_value) { |
318 | | /* Release the data block, if it was protected */ |
319 | 0 | if (dblock && |
320 | 0 | H5AC_unprotect(hdr->f, H5AC_EARRAY_DBLOCK, dblock->addr, dblock, H5AC__NO_FLAGS_SET) < 0) |
321 | 0 | HDONE_ERROR(H5E_EARRAY, H5E_CANTUNPROTECT, NULL, |
322 | 0 | "unable to unprotect extensible array data block, address = %llu", |
323 | 0 | (unsigned long long)dblock->addr); |
324 | 0 | } |
325 | |
|
326 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
327 | 0 | } /* end H5EA__dblock_protect() */ |
328 | | |
329 | | /*------------------------------------------------------------------------- |
330 | | * Function: H5EA__dblock_unprotect |
331 | | * |
332 | | * Purpose: Convenience wrapper around unprotecting extensible array data block |
333 | | * |
334 | | * Return: Non-negative on success/Negative on failure |
335 | | * |
336 | | *------------------------------------------------------------------------- |
337 | | */ |
338 | | herr_t |
339 | | H5EA__dblock_unprotect(H5EA_dblock_t *dblock, unsigned cache_flags) |
340 | 0 | { |
341 | 0 | herr_t ret_value = SUCCEED; |
342 | |
|
343 | 0 | FUNC_ENTER_PACKAGE |
344 | | |
345 | | /* Sanity check */ |
346 | 0 | assert(dblock); |
347 | | |
348 | | /* Unprotect the data block */ |
349 | 0 | if (H5AC_unprotect(dblock->hdr->f, H5AC_EARRAY_DBLOCK, dblock->addr, dblock, cache_flags) < 0) |
350 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTUNPROTECT, FAIL, |
351 | 0 | "unable to unprotect extensible array data block, address = %llu", |
352 | 0 | (unsigned long long)dblock->addr); |
353 | | |
354 | 0 | done: |
355 | |
|
356 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
357 | 0 | } /* end H5EA__dblock_unprotect() */ |
358 | | |
359 | | /*------------------------------------------------------------------------- |
360 | | * Function: H5EA__dblock_delete |
361 | | * |
362 | | * Purpose: Delete a data block |
363 | | * |
364 | | * Return: SUCCEED/FAIL |
365 | | * |
366 | | *------------------------------------------------------------------------- |
367 | | */ |
368 | | herr_t |
369 | | H5EA__dblock_delete(H5EA_hdr_t *hdr, void *parent, haddr_t dblk_addr, size_t dblk_nelmts) |
370 | 0 | { |
371 | 0 | H5EA_dblock_t *dblock = NULL; /* Pointer to data block */ |
372 | 0 | herr_t ret_value = SUCCEED; |
373 | |
|
374 | 0 | FUNC_ENTER_PACKAGE |
375 | | |
376 | | /* Sanity check */ |
377 | 0 | assert(hdr); |
378 | 0 | assert(parent); |
379 | 0 | assert(H5_addr_defined(dblk_addr)); |
380 | 0 | assert(dblk_nelmts > 0); |
381 | | |
382 | | /* Protect data block */ |
383 | 0 | if (NULL == (dblock = H5EA__dblock_protect(hdr, parent, dblk_addr, dblk_nelmts, H5AC__NO_FLAGS_SET))) |
384 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTPROTECT, FAIL, |
385 | 0 | "unable to protect extensible array data block, address = %llu", |
386 | 0 | (unsigned long long)dblk_addr); |
387 | | |
388 | | /* Check if this is a paged data block */ |
389 | 0 | if (dblk_nelmts > hdr->dblk_page_nelmts) { |
390 | 0 | size_t npages = dblk_nelmts / hdr->dblk_page_nelmts; /* Number of pages in data block */ |
391 | 0 | haddr_t dblk_page_addr; /* Address of each data block page */ |
392 | 0 | size_t dblk_page_size; /* Size of each data block page */ |
393 | 0 | size_t u; /* Local index variable */ |
394 | | |
395 | | /* Set up initial state */ |
396 | 0 | dblk_page_addr = dblk_addr + H5EA_DBLOCK_PREFIX_SIZE(dblock); |
397 | 0 | dblk_page_size = (hdr->dblk_page_nelmts * hdr->cparam.raw_elmt_size) + H5EA_SIZEOF_CHKSUM; |
398 | | |
399 | | /* Iterate over pages in data block */ |
400 | 0 | for (u = 0; u < npages; u++) { |
401 | | /* Evict the data block page from the metadata cache */ |
402 | | /* (OK to call if it doesn't exist in the cache) */ |
403 | 0 | if (H5AC_expunge_entry(hdr->f, H5AC_EARRAY_DBLK_PAGE, dblk_page_addr, H5AC__NO_FLAGS_SET) < 0) |
404 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTEXPUNGE, FAIL, |
405 | 0 | "unable to remove array data block page from metadata cache"); |
406 | | |
407 | | /* Advance to next page address */ |
408 | 0 | dblk_page_addr += dblk_page_size; |
409 | 0 | } /* end for */ |
410 | 0 | } /* end if */ |
411 | | |
412 | 0 | done: |
413 | | /* Finished deleting data block in metadata cache */ |
414 | 0 | if (dblock && H5EA__dblock_unprotect(dblock, H5AC__DIRTIED_FLAG | H5AC__DELETED_FLAG | |
415 | 0 | H5AC__FREE_FILE_SPACE_FLAG) < 0) |
416 | 0 | HDONE_ERROR(H5E_EARRAY, H5E_CANTUNPROTECT, FAIL, "unable to release extensible array data block"); |
417 | |
|
418 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
419 | 0 | } /* end H5EA__dblock_delete() */ |
420 | | |
421 | | /*------------------------------------------------------------------------- |
422 | | * Function: H5EA__dblock_dest |
423 | | * |
424 | | * Purpose: Destroys an extensible array data block in memory. |
425 | | * |
426 | | * Return: Non-negative on success/Negative on failure |
427 | | * |
428 | | *------------------------------------------------------------------------- |
429 | | */ |
430 | | herr_t |
431 | | H5EA__dblock_dest(H5EA_dblock_t *dblock) |
432 | 0 | { |
433 | 0 | herr_t ret_value = SUCCEED; |
434 | |
|
435 | 0 | FUNC_ENTER_PACKAGE |
436 | | |
437 | | /* Sanity check */ |
438 | 0 | assert(dblock); |
439 | 0 | assert(!dblock->has_hdr_depend); |
440 | | |
441 | | /* Check if shared header field has been initialized */ |
442 | 0 | if (dblock->hdr) { |
443 | | /* Check if we've got elements in the data block */ |
444 | 0 | if (dblock->elmts && !dblock->npages) { |
445 | | /* Free buffer for data block elements */ |
446 | 0 | assert(dblock->nelmts > 0); |
447 | 0 | if (H5EA__hdr_free_elmts(dblock->hdr, dblock->nelmts, dblock->elmts) < 0) |
448 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTFREE, FAIL, |
449 | 0 | "unable to free extensible array data block element buffer"); |
450 | 0 | dblock->elmts = NULL; |
451 | 0 | dblock->nelmts = 0; |
452 | 0 | } /* end if */ |
453 | | |
454 | | /* Decrement reference count on shared info */ |
455 | 0 | if (H5EA__hdr_decr(dblock->hdr) < 0) |
456 | 0 | HGOTO_ERROR(H5E_EARRAY, H5E_CANTDEC, FAIL, |
457 | 0 | "can't decrement reference count on shared array header"); |
458 | 0 | dblock->hdr = NULL; |
459 | 0 | } /* end if */ |
460 | | |
461 | | /* Sanity check */ |
462 | 0 | assert(NULL == dblock->top_proxy); |
463 | | |
464 | | /* Free the data block itself */ |
465 | 0 | dblock = H5FL_FREE(H5EA_dblock_t, dblock); |
466 | |
|
467 | 0 | done: |
468 | 0 | FUNC_LEAVE_NOAPI(ret_value) |
469 | 0 | } /* end H5EA__dblock_dest() */ |