diff options
author | Philip Guenthe <guenther@cvs.openbsd.org> | 2009-11-19 08:06:07 +0000 |
---|---|---|
committer | Philip Guenthe <guenther@cvs.openbsd.org> | 2009-11-19 08:06:07 +0000 |
commit | 8b12ee1bb78ac0a27cbe0aa1285ad6c24a55095e (patch) | |
tree | 0cc36b3ca78a96095f17f206a7605922a381c238 | |
parent | dc4c19c59c4f534146b68f5578a7c2454b8ae98e (diff) |
Add regression tests for stdio threading. Originally by blambert with
some further hacking by me
-rw-r--r-- | regress/lib/libc/Makefile | 3 | ||||
-rw-r--r-- | regress/lib/libc/stdio_threading/Makefile | 3 | ||||
-rw-r--r-- | regress/lib/libc/stdio_threading/fgetln/Makefile | 6 | ||||
-rwxr-xr-x | regress/lib/libc/stdio_threading/fgetln/fgetln_test.c | 70 | ||||
-rw-r--r-- | regress/lib/libc/stdio_threading/fgets/Makefile | 6 | ||||
-rwxr-xr-x | regress/lib/libc/stdio_threading/fgets/fgets_test.c | 69 | ||||
-rw-r--r-- | regress/lib/libc/stdio_threading/fopen/Makefile | 6 | ||||
-rwxr-xr-x | regress/lib/libc/stdio_threading/fopen/fopen_test.c | 48 | ||||
-rw-r--r-- | regress/lib/libc/stdio_threading/fputs/Makefile | 6 | ||||
-rwxr-xr-x | regress/lib/libc/stdio_threading/fputs/fputs_test.c | 66 | ||||
-rw-r--r-- | regress/lib/libc/stdio_threading/fread/Makefile | 6 | ||||
-rwxr-xr-x | regress/lib/libc/stdio_threading/fread/fread_test.c | 71 | ||||
-rw-r--r-- | regress/lib/libc/stdio_threading/fwrite/Makefile | 6 | ||||
-rwxr-xr-x | regress/lib/libc/stdio_threading/fwrite/fwrite_test.c | 66 | ||||
-rw-r--r-- | regress/lib/libc/stdio_threading/include/local.h | 80 |
15 files changed, 511 insertions, 1 deletions
diff --git a/regress/lib/libc/Makefile b/regress/lib/libc/Makefile index cca83b0a868..b210b7df719 100644 --- a/regress/lib/libc/Makefile +++ b/regress/lib/libc/Makefile @@ -1,9 +1,10 @@ -# $OpenBSD: Makefile,v 1.28 2008/10/02 12:26:45 millert Exp $ +# $OpenBSD: Makefile,v 1.29 2009/11/19 08:06:06 guenther Exp $ SUBDIR+= _setjmp alloca atexit basename cxa-atexit db dirname fnmatch SUBDIR+= fpclassify getaddrinfo getcap getopt_long glob hsearch longjmp SUBDIR+= locale malloc netdb popen printf regex setjmp setjmp-signal SUBDIR+= sigreturn sigsetjmp sprintf strerror strtod strtonum telldir time vis +SUBDIR+= stdio_threading .if (${MACHINE_ARCH} != "vax") SUBDIR+= ieeefp diff --git a/regress/lib/libc/stdio_threading/Makefile b/regress/lib/libc/stdio_threading/Makefile new file mode 100644 index 00000000000..e42481afc2f --- /dev/null +++ b/regress/lib/libc/stdio_threading/Makefile @@ -0,0 +1,3 @@ +SUBDIR += fopen fread fwrite fgetln fgets fputs + +.include <bsd.subdir.mk> diff --git a/regress/lib/libc/stdio_threading/fgetln/Makefile b/regress/lib/libc/stdio_threading/fgetln/Makefile new file mode 100644 index 00000000000..216db188ef0 --- /dev/null +++ b/regress/lib/libc/stdio_threading/fgetln/Makefile @@ -0,0 +1,6 @@ +TOPDIR=${.CURDIR} +PROG=fgetln_test +CFLAGS+=-I ${TOPDIR}/../include/ +LDFLAGS+=-lpthread + +.include <bsd.regress.mk> diff --git a/regress/lib/libc/stdio_threading/fgetln/fgetln_test.c b/regress/lib/libc/stdio_threading/fgetln/fgetln_test.c new file mode 100755 index 00000000000..d5c4db2edf5 --- /dev/null +++ b/regress/lib/libc/stdio_threading/fgetln/fgetln_test.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2008 Bret S. Lambert <blambert@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "local.h" + +void +fgetln_thread(void *v) +{ + FILE *file = v; + size_t len; + char *buf; + int i; + + for (i = 0; i < 4096; i++) { + if ((buf = fgetln(file, &len)) == NULL) { + + if (feof(file)) + break; + + printf("OMG!!!\n"); + fflush(stdout); + break; + } + if (strncmp(buf, TEXT_N, sizeof(TEXT_N))) + err(1, "fgetln not atomic!!!"); + } +} + +int +main(void) +{ + char sfn[24]; + char buf[sizeof(TEXT_N)]; + FILE *sfp; + int fd, i; + + strlcpy(sfn, "/tmp/barnacles.XXXXXXXX", sizeof(sfn)); + if ((fd = mkstemp(sfn)) == -1 || + (sfp = fdopen(fd, "w+")) == NULL) { + if (fd != -1) { + unlink(sfn); + close(fd); + } + err(1, "could not open temporary file"); + } + + for (i = 0; i < 4096 * THREAD_COUNT; i++) + if (fwrite(TEXT_N, sizeof(char), strlen(TEXT_N), sfp) == 0) + err(1, "Could not populate test file"); + + run_threads(fgetln_thread, sfp); + + unlink(sfn); + close(fd); + + exit(0); +} diff --git a/regress/lib/libc/stdio_threading/fgets/Makefile b/regress/lib/libc/stdio_threading/fgets/Makefile new file mode 100644 index 00000000000..dfd210cbf41 --- /dev/null +++ b/regress/lib/libc/stdio_threading/fgets/Makefile @@ -0,0 +1,6 @@ +TOPDIR=${.CURDIR} +PROG=fgets_test +CFLAGS+=-I ${TOPDIR}/../include/ +LDFLAGS+=-lpthread + +.include <bsd.regress.mk> diff --git a/regress/lib/libc/stdio_threading/fgets/fgets_test.c b/regress/lib/libc/stdio_threading/fgets/fgets_test.c new file mode 100755 index 00000000000..685d4c8257d --- /dev/null +++ b/regress/lib/libc/stdio_threading/fgets/fgets_test.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2008 Bret S. Lambert <blambert@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "local.h" + +void +fgets_thread(void *v) +{ + char buf[sizeof(TEXT_N) + 1]; + FILE *file = v; + int i; + + for (i = 0; i < 4096; i++) { + if (fgets(buf, sizeof(buf), file) == NULL) { + + if (feof(file)) + break; + + printf("OMG!!!\n"); + fflush(stdout); + break; + } + if (strncmp(buf, TEXT, sizeof(TEXT))) + err(1, "Read not atomic!!!"); + } +} + +int +main(void) +{ + char sfn[24]; + char buf[sizeof(TEXT)]; + FILE *sfp; + int fd, i; + + strlcpy(sfn, "/tmp/barnacles.XXXXXXXX", sizeof(sfn)); + if ((fd = mkstemp(sfn)) == -1 || + (sfp = fdopen(fd, "w+")) == NULL) { + if (fd != -1) { + unlink(sfn); + close(fd); + } + err(1, "could not open temporary file"); + } + + for (i = 0; i < 4096 * THREAD_COUNT; i++) + if (fwrite(TEXT_N, sizeof(char), strlen(TEXT_N), sfp) == 0) + err(1, "Could not populate test file"); + + run_threads(fgets_thread, sfp); + + unlink(sfn); + close(fd); + + exit(0); +} diff --git a/regress/lib/libc/stdio_threading/fopen/Makefile b/regress/lib/libc/stdio_threading/fopen/Makefile new file mode 100644 index 00000000000..4301a83978f --- /dev/null +++ b/regress/lib/libc/stdio_threading/fopen/Makefile @@ -0,0 +1,6 @@ +TOPDIR=${.CURDIR} +PROG=fopen_test +CFLAGS+=-I ${TOPDIR}/../include/ +LDFLAGS+=-lpthread + +.include <bsd.regress.mk> diff --git a/regress/lib/libc/stdio_threading/fopen/fopen_test.c b/regress/lib/libc/stdio_threading/fopen/fopen_test.c new file mode 100755 index 00000000000..72359bb323f --- /dev/null +++ b/regress/lib/libc/stdio_threading/fopen/fopen_test.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2008 Bret S. Lambert <blambert@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdio.h> +#include <pthread.h> +#include "local.h" + +int +writefn(void *cookie, const char *buf, int size) +{ + return 0; +} + +void +fopen_thread(void *v) +{ + FILE *file; + int i; + + for (i = 0; i < 4096; i++) { + file = fwopen(&i, writefn); + if (file != NULL) { + fputc('0', file); + pthread_yield(); + fclose(file); + } + } +} + +int +main(void) +{ + run_threads(fopen_thread, NULL); + exit(0); +} diff --git a/regress/lib/libc/stdio_threading/fputs/Makefile b/regress/lib/libc/stdio_threading/fputs/Makefile new file mode 100644 index 00000000000..5542dd3979d --- /dev/null +++ b/regress/lib/libc/stdio_threading/fputs/Makefile @@ -0,0 +1,6 @@ +TOPDIR=${.CURDIR} +PROG=fputs_test +CFLAGS+=-I ${TOPDIR}/../include/ +LDFLAGS+=-lpthread + +.include <bsd.regress.mk> diff --git a/regress/lib/libc/stdio_threading/fputs/fputs_test.c b/regress/lib/libc/stdio_threading/fputs/fputs_test.c new file mode 100755 index 00000000000..c0a617510e9 --- /dev/null +++ b/regress/lib/libc/stdio_threading/fputs/fputs_test.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2008 Bret S. Lambert <blambert@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "local.h" + +void +fputs_thread(void *v) +{ + FILE *file = v; + int i; + + for (i = 0; i < 4096; i++) { + if (fputs(TEXT, file) != 0) { + + if (feof(file)) + break; + + printf("OMG!!!\n"); + fflush(stdout); + break; + } + } +} + +int +main(void) +{ + char sfn[24]; + char buf[sizeof(TEXT)]; + FILE *sfp; + int fd, i; + + strlcpy(sfn, "/tmp/barnacles.XXXXXXXX", sizeof(sfn)); + if ((fd = mkstemp(sfn)) == -1 || + (sfp = fdopen(fd, "w+")) == NULL) { + if (fd != -1) { + unlink(sfn); + close(fd); + } + err(1, "could not open temporary file"); + } + + run_threads(fputs_thread, sfp); + + while (fgets(buf, sizeof(buf), sfp) != NULL) /* verify */ + if (strcmp(buf, TEXT)) + err(1, "Thread writes were not atomic!!!"); + + unlink(sfn); + close(fd); + + exit(0); +} diff --git a/regress/lib/libc/stdio_threading/fread/Makefile b/regress/lib/libc/stdio_threading/fread/Makefile new file mode 100644 index 00000000000..97c8816652e --- /dev/null +++ b/regress/lib/libc/stdio_threading/fread/Makefile @@ -0,0 +1,6 @@ +TOPDIR=${.CURDIR} +PROG=fread_test +CFLAGS+=-I ${TOPDIR}/../include/ +LDFLAGS+=-lpthread + +.include <bsd.regress.mk> diff --git a/regress/lib/libc/stdio_threading/fread/fread_test.c b/regress/lib/libc/stdio_threading/fread/fread_test.c new file mode 100755 index 00000000000..b2372f5ab60 --- /dev/null +++ b/regress/lib/libc/stdio_threading/fread/fread_test.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2008 Bret S. Lambert <blambert@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdio.h> +#include <pthread.h> +#include "local.h" + +void +fread_thread(void *v) +{ + char buf[sizeof(TEXT)]; + FILE *file = v; + int i; + + for (i = 0; i < 4096; i++) { + if (fread(buf, sizeof(char), strlen(TEXT), file) == 0) { + + if (feof(file)) + break; + + printf("OMG!!!\n"); + fflush(stdout); + break; + } + if (strncmp(buf, TEXT, sizeof(TEXT))) + err(1, "Read not atomic!!!"); + } +} + +int +main(void) +{ + char sfn[24]; + char buf[sizeof(TEXT)]; + FILE *sfp; + int fd, i; + + strlcpy(sfn, "/tmp/barnacles.XXXXXXXX", sizeof(sfn)); + if ((fd = mkstemp(sfn)) == -1 || + (sfp = fdopen(fd, "w+")) == NULL) { + if (fd != -1) { + unlink(sfn); + close(fd); + } + err(1, "could not open temporary file"); + } + + for (i = 0; i < 4096 * THREAD_COUNT; i++) + if (fwrite(TEXT, sizeof(char), strlen(TEXT), sfp) == 0) + err(1, "Could not populate test file"); + + run_threads(fread_thread, sfp); + + unlink(sfn); + close(fd); + + exit(0); +} diff --git a/regress/lib/libc/stdio_threading/fwrite/Makefile b/regress/lib/libc/stdio_threading/fwrite/Makefile new file mode 100644 index 00000000000..5952a1f48a0 --- /dev/null +++ b/regress/lib/libc/stdio_threading/fwrite/Makefile @@ -0,0 +1,6 @@ +TOPDIR=${.CURDIR} +PROG=fwrite_test +CFLAGS+=-I ${TOPDIR}/../include/ +LDFLAGS+=-lpthread + +.include <bsd.regress.mk> diff --git a/regress/lib/libc/stdio_threading/fwrite/fwrite_test.c b/regress/lib/libc/stdio_threading/fwrite/fwrite_test.c new file mode 100755 index 00000000000..39bfb51ef0b --- /dev/null +++ b/regress/lib/libc/stdio_threading/fwrite/fwrite_test.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2008 Bret S. Lambert <blambert@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "local.h" + +void +fwrite_thread(void *v) +{ + FILE *file = v; + int i; + + for (i = 0; i < 4096; i++) { + if (fwrite(TEXT, sizeof(char), strlen(TEXT), file) == 0) { + + if (feof(file)) + break; + + printf("OMG!!!\n"); + fflush(stdout); + break; + } + } +} + +int +main(void) +{ + char sfn[24]; + char buf[sizeof(TEXT)]; + FILE *sfp; + int fd, i; + + strlcpy(sfn, "/tmp/barnacles.XXXXXXXX", sizeof(sfn)); + if ((fd = mkstemp(sfn)) == -1 || + (sfp = fdopen(fd, "w+")) == NULL) { + if (fd != -1) { + unlink(sfn); + close(fd); + } + err(1, "could not open temporary file"); + } + + run_threads(fwrite_thread, sfp); + + while (fread(buf, sizeof(char), strlen(TEXT), sfp)) /* verify */ + if (strncmp(buf, TEXT, sizeof(TEXT))) + err(1, "Thread writes were not atomic!!!"); + + unlink(sfn); + close(fd); + + exit(0); +} diff --git a/regress/lib/libc/stdio_threading/include/local.h b/regress/lib/libc/stdio_threading/include/local.h new file mode 100644 index 00000000000..794d8680719 --- /dev/null +++ b/regress/lib/libc/stdio_threading/include/local.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2008 Bret S. Lambert <blambert@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <pthread.h> + +#define THREAD_COUNT 64 + +#define TEXT "barnacles" +#define TEXT_N "barnacles\n" + +void run_threads(void (*)(void *), void *); + +static pthread_rwlock_t start; +static void (*real_func)(void *); + +static void * +thread(void *arg) +{ + int r; + + if ((r = pthread_rwlock_rdlock(&start))) + errx(1, "could not obtain lock in thread: %s", strerror(r)); + real_func(arg); + if ((r = pthread_rwlock_unlock(&start))) + errx(1, "could not release lock in thread: %s", strerror(r)); + return NULL; +} + +void +run_threads(void (*func)(void *), void *arg) +{ + pthread_t self, pthread[THREAD_COUNT]; + int i, r; + + self = pthread_self(); + real_func = func; + if ((r = pthread_rwlock_init(&start, NULL))) + errx(1, "could not initialize lock: %s", strerror(r)); + + if ((r = pthread_rwlock_wrlock(&start))) /* block */ + errx(1, "could not lock lock: %s", strerror(r)); + + for (i = 0; i < THREAD_COUNT; i++) + if ((r = pthread_create(&pthread[i], NULL, thread, arg))) { + warnx("could not create thread: %s", strerror(r)); + pthread[i] = self; + } + + + if ((r = pthread_rwlock_unlock(&start))) /* unleash */ + errx(1, "could not release lock: %s", strerror(r)); + + sleep(1); + + if ((r = pthread_rwlock_wrlock(&start))) /* sync */ + errx(1, "parent could not sync with children: %s", + strerror(r)); + + for (i = 0; i < THREAD_COUNT; i++) + if (! pthread_equal(pthread[i], self) && + (r = pthread_join(pthread[i], NULL))) + warnx("could not join thread: %s", strerror(r)); +} + |