diff --git a/Doc/library/asyncio-protocol.rst b/Doc/library/asyncio-protocol.rst index 58f77feb3119841..42c2d509d5a729b 100644 --- a/Doc/library/asyncio-protocol.rst +++ b/Doc/library/asyncio-protocol.rst @@ -333,6 +333,9 @@ Write-only Transports This method does not block; it buffers the data and arranges for it to be sent out asynchronously. + The transport keeps a reference to *data* rather than copying. *data* + must not be mutated while being sent. + .. method:: WriteTransport.writelines(list_of_data) Write a list (or any iterable) of data bytes to the transport. diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index 076183139b28ae4..5bbb6d433ee2ad6 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -663,14 +663,6 @@ class timedelta: def __new__(cls, days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0): - # Doing this efficiently and accurately in C is going to be difficult - # and error-prone, due to ubiquitous overflow possibilities, and that - # C double doesn't have enough bits of precision to represent - # microseconds over 10K years faithfully. The code here tries to make - # explicit where go-fast assumptions can be relied on, in order to - # guide the C implementation; it's way more convoluted than speed- - # ignoring auto-overflow-to-long idiomatic Python could be. - for name, value in ( ("days", days), ("seconds", seconds), @@ -685,80 +677,35 @@ def __new__(cls, days=0, seconds=0, microseconds=0, f"unsupported type for timedelta {name} component: {type(value).__name__}" ) - # Final values, all integer. - # s and us fit in 32-bit signed ints; d isn't bounded. - d = s = us = 0 - - # Normalize everything to days, seconds, microseconds. - days += weeks*7 - seconds += minutes*60 + hours*3600 - microseconds += milliseconds*1000 - - # Get rid of all fractions, and normalize s and us. - # Take a deep breath . - if isinstance(days, float): - dayfrac, days = _math.modf(days) - daysecondsfrac, daysecondswhole = _math.modf(dayfrac * (24.*3600.)) - assert daysecondswhole == int(daysecondswhole) # can't overflow - s = int(daysecondswhole) - assert days == int(days) - d = int(days) - else: - daysecondsfrac = 0.0 - d = days - assert isinstance(daysecondsfrac, float) - assert abs(daysecondsfrac) <= 1.0 - assert isinstance(d, int) - assert abs(s) <= 24 * 3600 - # days isn't referenced again before redefinition - - if isinstance(seconds, float): - secondsfrac, seconds = _math.modf(seconds) - assert seconds == int(seconds) - seconds = int(seconds) - secondsfrac += daysecondsfrac - assert abs(secondsfrac) <= 2.0 - else: - secondsfrac = daysecondsfrac - # daysecondsfrac isn't referenced again - assert isinstance(secondsfrac, float) - assert abs(secondsfrac) <= 2.0 - - assert isinstance(seconds, int) - days, seconds = divmod(seconds, 24*3600) - d += days - s += int(seconds) # can't overflow - assert isinstance(s, int) - assert abs(s) <= 2 * 24 * 3600 - # seconds isn't referenced again before redefinition - - usdouble = secondsfrac * 1e6 - assert abs(usdouble) < 2.1e6 # exact value not critical - # secondsfrac isn't referenced again - - if isinstance(microseconds, float): - microseconds = round(microseconds + usdouble) - seconds, microseconds = divmod(microseconds, 1000000) - days, seconds = divmod(seconds, 24*3600) - d += days - s += seconds - else: - microseconds = int(microseconds) - seconds, microseconds = divmod(microseconds, 1000000) - days, seconds = divmod(seconds, 24*3600) - d += days - s += seconds - microseconds = round(microseconds + usdouble) - assert isinstance(s, int) - assert isinstance(microseconds, int) - assert abs(s) <= 3 * 24 * 3600 - assert abs(microseconds) < 3.1e6 - - # Just a little bit of carrying possible for microseconds and seconds. - seconds, us = divmod(microseconds, 1000000) - s += seconds - days, s = divmod(s, 24*3600) - d += days + # Sum the exact number of microseconds, like the C implementation: + # the integer part of each float is converted exactly, and only the + # fractional microseconds left over are added up as a float and + # rounded (half to even) at the end. + us = 0 + leftover = 0.0 + for value, factor in ( + (microseconds, 1), + (milliseconds, 1000), + (seconds, 1000000), + (minutes, 60*1000000), + (hours, 3600*1000000), + (days, 24*3600*1000000), + (weeks, 7*24*3600*1000000) + ): + if isinstance(value, float): + frac, whole = _math.modf(value) + us += int(whole) * factor + frac, whole = _math.modf(frac * factor) + us += int(whole) + leftover += frac + else: + us += int(value * factor) + if leftover: + n, m = leftover.as_integer_ratio() + us = _divide_and_round(us * m + n, m) + + s, us = divmod(us, 1000000) + d, s = divmod(s, 24*3600) assert isinstance(d, int) assert isinstance(s, int) and 0 <= s < 24*3600 diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 57ba2f13246c946..15a3754262d078d 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -1099,7 +1099,9 @@ def write(self, data): # Not all was written; register write handler. self._add_writer(self._sock_fd, self._write_ready) - # Add it to the buffer. + # Add it to the buffer as a view, so partial sends don't copy it. + if not isinstance(data, memoryview): + data = memoryview(data) self._buffer.append(data) self._buffer_size += len(data) self._maybe_pause_protocol() diff --git a/Lib/asyncio/sslproto.py b/Lib/asyncio/sslproto.py index 1bf2fffcade18d3..59e2d6bcff8fa5b 100644 --- a/Lib/asyncio/sslproto.py +++ b/Lib/asyncio/sslproto.py @@ -308,6 +308,9 @@ def __init__(self, loop, app_protocol, sslcontext, waiter, # App data write buffering self._write_backlog = collections.deque() self._write_buffer_size = 0 + # How much of self._outgoing was produced by _do_write(): encrypted + # app data, plus anything else OpenSSL flushed along with it + self._outgoing_app_size = 0 self._waiter = waiter self._loop = loop @@ -720,6 +723,7 @@ def _write_appdata(self, list_of_data): self._fatal_error(ex, 'Fatal error on SSL protocol') def _do_write(self): + pending = self._outgoing.pending try: while self._write_backlog: data = self._write_backlog[0] @@ -733,11 +737,13 @@ def _do_write(self): self._write_buffer_size -= data_len except SSLAgainErrors: pass + self._outgoing_app_size += self._outgoing.pending - pending self._process_outgoing() def _process_outgoing(self): if not self._ssl_writing_paused: data = self._outgoing.read() + self._outgoing_app_size = 0 if len(data): self._transport.write(data) self._control_app_writing() @@ -904,7 +910,15 @@ def resume(): # Flow control for reads from SSL socket def _control_ssl_reading(self): - size = self._get_read_buffer_size() + # Also count the records OpenSSL writes on its own while reading + # (alerts, KeyUpdate replies, ...) which the peer does not read: + # the app is not involved in producing them, so pausing it does + # not bound them; only pausing reads from the peer does. Whatever + # was produced while writing app data (_outgoing_app_size) is left + # out so that two peers which both write before they read cannot + # deadlock. + size = (self._get_read_buffer_size() + + self._outgoing.pending - self._outgoing_app_size) if size >= self._incoming_high_water and not self._ssl_reading_paused: self._ssl_reading_paused = True self._transport.pause_reading() @@ -937,6 +951,7 @@ def resume_writing(self): assert self._ssl_writing_paused self._ssl_writing_paused = False self._process_outgoing() + self._control_ssl_reading() def _fatal_error(self, exc, message='Fatal error on transport'): if self._transport: diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 53285d918c17cad..92607e381db20c7 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -554,6 +554,13 @@ def test_constructor(self): eq(td(seconds=0.001), td(milliseconds=1)) eq(td(milliseconds=0.001), td(microseconds=1)) + # Large float args keep their sub-second part + eq(td(weeks=12345678.9), td(86419752, 25920, 225)) + eq(td(hours=123456789.1), td(5144032, 75959, 999979)) + eq(td(minutes=1234567890.1), td(857338, 70205, 999994)) + eq(td(days=1.1, weeks=1000000.1), td(7000001, 69119, 999986)) + eq(td(seconds=0.5, microseconds=2.0**60), td(13343998, 77407, 346976)) + # Check type of args to constructor ra(TypeError, lambda: td(weeks='1')) ra(TypeError, lambda: td(days='1')) diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py index 74322105a7c7577..e080225bd976d93 100644 --- a/Lib/test/test_asyncio/test_selector_events.py +++ b/Lib/test/test_asyncio/test_selector_events.py @@ -855,14 +855,23 @@ def test_write_partial_none(self): self.assertEqual(list_to_buffer([b'data']), transport._buffer) def test_write_tryagain(self): - self.sock.send.side_effect = BlockingIOError - data = b'data' - transport = self.socket_transport() - transport.write(data) - - self.loop.assert_writer(7, transport._write_ready) - self.assertEqual(list_to_buffer([b'data']), transport._buffer) + for buf in data, bytearray(data), memoryview(data): + with self.subTest(type=type(buf).__name__): + self.sock.send.side_effect = BlockingIOError + + transport = self.socket_transport() + transport.write(buf) + + self.loop.assert_writer(7, transport._write_ready) + self.assertEqual(list_to_buffer([b'data']), transport._buffer) + # gh-159503: the data is buffered as a view, not a copy + self.assertIsInstance(transport._buffer[0], memoryview) + self.assertIs(transport._buffer[0].obj, memoryview(buf).obj) + if isinstance(buf, bytearray): + # The buffered view pins the bytearray against resizing. + with self.assertRaises(BufferError): + buf.append(0) def test_write_sendmsg_no_data(self): self.sock.sendmsg = mock.Mock() diff --git a/Lib/test/test_asyncio/test_sslproto.py b/Lib/test/test_asyncio/test_sslproto.py index 08f680893ad7926..c8c9eec59e1dbb6 100644 --- a/Lib/test/test_asyncio/test_sslproto.py +++ b/Lib/test/test_asyncio/test_sslproto.py @@ -289,6 +289,46 @@ def test_write_after_closing(self): # should not raise self.assertIsNone(transp.write(b'data')) + def test_pause_reading_if_ssl_replies_are_not_read(self): + # Output which OpenSSL produces on its own while reading (alerts, + # KeyUpdate, ...) must not pile up if the peer does not read it. + ssl_proto = self.ssl_protocol() + transport = self.connection_made(ssl_proto, do_handshake=mock.Mock()) + reply = b'x' * (ssl_proto._incoming_high_water // 2) + + def read(*args): + ssl_proto._outgoing.write(reply) + raise ssl.SSLWantReadError + ssl_proto._sslobj.read.side_effect = read + + ssl_proto.pause_writing() + ssl_proto.buffer_updated(0) + transport.pause_reading.assert_not_called() + ssl_proto.buffer_updated(0) + transport.pause_reading.assert_called_once_with() + transport.resume_reading.assert_not_called() + transport.write.assert_not_called() + + ssl_proto.resume_writing() + transport.write.assert_called_once_with(reply * 2) + transport.resume_reading.assert_called_once_with() + + def test_no_pause_reading_if_app_data_is_not_read(self): + # Peers which both write a lot before they read must not deadlock. + ssl_proto = self.ssl_protocol() + transport = self.connection_made(ssl_proto, do_handshake=mock.Mock()) + ssl_proto._sslobj.write.side_effect = ssl_proto._outgoing.write + data = b'x' * ssl_proto._incoming_high_water + + ssl_proto.pause_writing() + ssl_proto._app_transport.write(data) + ssl_proto.buffer_updated(0) + transport.pause_reading.assert_not_called() + + ssl_proto.resume_writing() + transport.write.assert_called_once_with(data) + transport.resume_reading.assert_not_called() + ############################################################################## # Start TLS Tests diff --git a/Lib/test/test_io/test_memoryio.py b/Lib/test/test_io/test_memoryio.py index 6a286e5a62ef44c..48337ae9e80a53c 100644 --- a/Lib/test/test_io/test_memoryio.py +++ b/Lib/test/test_io/test_memoryio.py @@ -591,6 +591,10 @@ def test_issue141311(self): self.assertEqual(sys.maxsize, memio.seek(sys.maxsize)) buf = bytearray(2) self.assertEqual(0, memio.readinto(buf)) + # Writing there must not overflow the buffer size. + memio.seek(sys.maxsize - 1) + self.assertRaises((MemoryError, OverflowError), memio.write, b"x") + self.assertEqual(memio.getvalue(), b"") def test_peek(self): buf = self.buftype("1234567890") @@ -984,6 +988,12 @@ def __index__(self): finally: size.result.release() + # Sizes above 2**31 - 1 must work where C long is 32-bit. + if sys.maxsize > 2**31: + stream = self.ioclass(b'abc') + self.assertEqual(stream.truncate(2**31), 2**31) + self.assertEqual(stream.getvalue(), b'abc') + def test_readinto_overlapping_buffer(self): with self.ioclass(b'abcd') as stream: with stream.getbuffer()[1:] as target: @@ -1150,6 +1160,12 @@ def __index__(self): with self.assertRaises(ValueError): stream.truncate(Size()) + # Sizes above 2**31 - 1 must work where C long is 32-bit. + if sys.maxsize > 2**31: + stream = self.ioclass("abc") + self.assertEqual(stream.truncate(2**31), 2**31) + self.assertEqual(stream.getvalue(), "abc") + # XXX: For the Python version of io.StringIO, this is highly # dependent on the encoding used for the underlying buffer. def test_widechar(self): diff --git a/Misc/NEWS.d/next/Library/2026-10-16-01-56-15.gh-issue-159503.WgYiow.rst b/Misc/NEWS.d/next/Library/2026-10-16-01-56-15.gh-issue-159503.WgYiow.rst new file mode 100644 index 000000000000000..158968d0c2a1cea --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-16-01-56-15.gh-issue-159503.WgYiow.rst @@ -0,0 +1,3 @@ +Fix quadratic copying in :mod:`asyncio` selector socket transports on partial +sends. A :class:`bytearray` passed to :meth:`~asyncio.WriteTransport.write` +now stays locked against resizing until it has been sent. diff --git a/Misc/NEWS.d/next/Library/2026-10-27-15-26-53.gh-issue-160781.4VSeQK.rst b/Misc/NEWS.d/next/Library/2026-10-27-15-26-53.gh-issue-160781.4VSeQK.rst new file mode 100644 index 000000000000000..f6463a5d5313690 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-27-15-26-53.gh-issue-160781.4VSeQK.rst @@ -0,0 +1,3 @@ +Fix :class:`io.BytesIO` raising :exc:`SystemError` instead of +:exc:`OverflowError` when writing at a position close to +:data:`sys.maxsize`. diff --git a/Misc/NEWS.d/next/Library/2026-10-27-15-27-32.gh-issue-160781.kARuHu.rst b/Misc/NEWS.d/next/Library/2026-10-27-15-27-32.gh-issue-160781.kARuHu.rst new file mode 100644 index 000000000000000..1f1e25d8b461b4e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-27-15-27-32.gh-issue-160781.kARuHu.rst @@ -0,0 +1,2 @@ +Fix :class:`io.StringIO` implementation of :meth:`io.IOBase.truncate` +rejecting sizes larger than ``2**31 - 1`` on Windows. diff --git a/Misc/NEWS.d/next/Library/2026-10-27-15-42-16.gh-issue-160781.VGLB9S.rst b/Misc/NEWS.d/next/Library/2026-10-27-15-42-16.gh-issue-160781.VGLB9S.rst new file mode 100644 index 000000000000000..73c8c177fc88282 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-27-15-42-16.gh-issue-160781.VGLB9S.rst @@ -0,0 +1,2 @@ +Fix :class:`io.BytesIO` implementation of :meth:`io.IOBase.truncate` rejecting +sizes larger than ``2**31 - 1`` on Windows. diff --git a/Misc/NEWS.d/next/Library/2026-10-28-07-34-05.gh-issue-160806.ZZRkhi.rst b/Misc/NEWS.d/next/Library/2026-10-28-07-34-05.gh-issue-160806.ZZRkhi.rst new file mode 100644 index 000000000000000..1eb916ce8e98431 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-28-07-34-05.gh-issue-160806.ZZRkhi.rst @@ -0,0 +1,3 @@ +Fix the pure-Python implementation of :class:`datetime.timedelta` losing +sub-second precision with large float arguments. It now gives the same +results as the C implementation. diff --git a/Misc/NEWS.d/next/Security/2026-10-22-08-08-57.gh-issue-160499.pf9H9g.rst b/Misc/NEWS.d/next/Security/2026-10-22-08-08-57.gh-issue-160499.pf9H9g.rst new file mode 100644 index 000000000000000..4fcfc9edd90b782 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-10-22-08-08-57.gh-issue-160499.pf9H9g.rst @@ -0,0 +1,5 @@ +Fix unbounded memory growth in :mod:`asyncio` TLS connections when a peer +stops reading but keeps sending records that make OpenSSL generate its own +replies (such as TLS 1.2 renegotiation alerts). Reading from the peer is now +paused when this output accumulates, instead of only when application data +does. diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c index 01f63f8a677d371..2571da4538363f1 100644 --- a/Modules/_io/bytesio.c +++ b/Modules/_io/bytesio.c @@ -221,6 +221,9 @@ resize_buffer_lock_held(bytesio *self, size_t size) /* Major upsize; resize up to exact size */ alloc = size + 1; } + if (alloc > PY_SSIZE_T_MAX) { + goto overflow; + } if (SHARED_BUF(self)) { if (unshare_buffer_lock_held(self, alloc) < 0) @@ -755,7 +758,7 @@ _io_BytesIO_truncate_impl(bytesio *self, PyObject *size) new_size = self->pos; } else { - new_size = PyLong_AsLong(size); + new_size = PyNumber_AsSsize_t(size, PyExc_OverflowError); if (new_size == -1 && PyErr_Occurred()) { return NULL; } diff --git a/Modules/_io/stringio.c b/Modules/_io/stringio.c index 558fd57aa93c954..7800a4fcfca4dd8 100644 --- a/Modules/_io/stringio.c +++ b/Modules/_io/stringio.c @@ -490,7 +490,7 @@ _io_StringIO_truncate_impl(stringio *self, PyObject *pos) size = self->pos; } else { - size = PyLong_AsLong(pos); + size = PyNumber_AsSsize_t(pos, PyExc_OverflowError); if (size == -1 && PyErr_Occurred()) { return NULL; } diff --git a/Platforms/emscripten/README.md b/Platforms/emscripten/README.md index ce230c4b74a2738..b1fccea0af9f87b 100644 --- a/Platforms/emscripten/README.md +++ b/Platforms/emscripten/README.md @@ -138,7 +138,7 @@ await createEmscriptenModule({ - Threading is disabled by default. The ``configure`` option ``--enable-wasm-pthreads`` adds compiler flag ``-pthread`` and - linker flags ``-sUSE_PTHREADS -sPROXY_TO_PTHREAD``. + linker flags ``-pthread -sPROXY_TO_PTHREAD``. - pthread support requires WASM threads and SharedArrayBuffer (bulk memory). The Node.JS runtime keeps a pool of web workers around. Each web worker uses several file descriptors (eventfd, epoll, pipe). diff --git a/Python/ceval.c b/Python/ceval.c index aba8d2c0c762edf..0d053cdc7a63e34 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -70,70 +70,6 @@ _Py_ReachedRecursionLimitWithMargin(PyThreadState *tstate, int margin_count) # define Py_C_STACK_SIZE 4000000 #endif -#if defined(__EMSCRIPTEN__) - -// Temporary workaround to make `pthread_getattr_np` work on Emscripten. -// Emscripten 4.0.6 will contain a fix: -// https://github.com/emscripten-core/emscripten/pull/23887 - -#include "emscripten/stack.h" - -#define pthread_attr_t workaround_pthread_attr_t -#define pthread_attr_init workaround_pthread_attr_init -#define pthread_getattr_np workaround_pthread_getattr_np -#define pthread_attr_getguardsize workaround_pthread_attr_getguardsize -#define pthread_attr_getstack workaround_pthread_attr_getstack -#define pthread_attr_destroy workaround_pthread_attr_destroy - -typedef struct { - void *_a_stackaddr; - size_t _a_stacksize, _a_guardsize; -} pthread_attr_t; - -extern __attribute__((__visibility__("hidden"))) unsigned __default_guardsize; - -int pthread_attr_init(pthread_attr_t *a) -{ - return 0; -} - -// Modified version of pthread_getattr_np from the upstream PR. - -int pthread_getattr_np(pthread_t thread, pthread_attr_t *attr) { - attr->_a_stackaddr = (void*)emscripten_stack_get_base(); - attr->_a_stacksize = emscripten_stack_get_base() - emscripten_stack_get_end(); - attr->_a_guardsize = __default_guardsize; - return 0; -} - -// These three functions copied without any changes from Emscripten libc. - -int pthread_attr_getguardsize(const pthread_attr_t *restrict a, size_t *restrict size) -{ - *size = a->_a_guardsize; - return 0; -} - -int pthread_attr_getstack(const pthread_attr_t *restrict a, void **restrict addr, size_t *restrict size) -{ -/// XXX musl is not standard-conforming? It should not report EINVAL if _a_stackaddr is zero, and it should -/// report EINVAL if a is null: http://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_attr_getstack.html - if (!a) return EINVAL; -// if (!a->_a_stackaddr) -// return EINVAL; - - *size = a->_a_stacksize; - *addr = (void *)(a->_a_stackaddr - *size); - return 0; -} - -int pthread_attr_destroy(pthread_attr_t *a) -{ - return 0; -} - -#endif - static void hardware_stack_limits(uintptr_t *base, uintptr_t *top, uintptr_t sp) { diff --git a/configure b/configure index a11cd0c27bcf0d0..cbeb23129c14754 100755 --- a/configure +++ b/configure @@ -5055,8 +5055,8 @@ fi case $host in #( wasm64-*-emscripten) : - as_fn_append CFLAGS " -sMEMORY64=1" - as_fn_append LDFLAGS " -sMEMORY64=1" + as_fn_append CFLAGS " -m64" + as_fn_append LDFLAGS " -m64" ;; #( *) : ;; @@ -10229,7 +10229,7 @@ fi then : as_fn_append CFLAGS_NODIST " -pthread" - as_fn_append LDFLAGS_NODIST " -sUSE_PTHREADS" + as_fn_append LDFLAGS_NODIST " -pthread" as_fn_append LINKFORSHARED " -sPROXY_TO_PTHREAD" fi diff --git a/configure.ac b/configure.ac index 640919bd07bd8db..8c79be7a4d0808a 100644 --- a/configure.ac +++ b/configure.ac @@ -1153,12 +1153,12 @@ if test -z "$CFLAGS"; then fi dnl Emscripten SDK and WASI SDK default to wasm32. -dnl On Emscripten use MEMORY64 setting to build target wasm64-emscripten. +dnl On Emscripten use -m64 to build target wasm64-emscripten. dnl for wasm64. AS_CASE([$host], [wasm64-*-emscripten], [ - AS_VAR_APPEND([CFLAGS], [" -sMEMORY64=1"]) - AS_VAR_APPEND([LDFLAGS], [" -sMEMORY64=1"]) + AS_VAR_APPEND([CFLAGS], [" -m64"]) + AS_VAR_APPEND([LDFLAGS], [" -m64"]) ], ) @@ -2659,7 +2659,7 @@ AS_CASE([$ac_sys_system], AS_VAR_IF([enable_wasm_pthreads], [yes], [ AS_VAR_APPEND([CFLAGS_NODIST], [" -pthread"]) - AS_VAR_APPEND([LDFLAGS_NODIST], [" -sUSE_PTHREADS"]) + AS_VAR_APPEND([LDFLAGS_NODIST], [" -pthread"]) AS_VAR_APPEND([LINKFORSHARED], [" -sPROXY_TO_PTHREAD"]) ]) dnl not completely sure whether or not we want -sEXIT_RUNTIME, keeping it for now.