Re: [VOTE] Release apr-1.7.4-rc1 as apr-1.7.4

2023-04-13 Thread Evgeny Kotkov via dev
Eric Covener  writes:

> Hi all,
>
> Please find below the proposed release tarball and signatures:
>
> https://dist.apache.org/repos/dist/dev/apr/
>
> I would like to call a VOTE over the next few days to release
> this candidate tarball apr-1.7.4-rc1 as 1.7.4:

  [X] +1: It's not just good, it's good enough!

Checks performed:
— Verify SHA256 for apr-1.7.4-rc1-candidate.tar.bz2
— Verify SHA256 for apr-1.7.4-rc1-candidate.tar.gz
— Verify SHA256 for apr-1.7.4-rc1-candidate-win32-src.zip
— Verify diff between apr-1.7.4-rc1-candidate-win32-src.zip and
  published apr-1.7.3-win32-src.zip
— Build using apr-1.7.4-rc1-candidate-win32-src.zip on Windows x64/CMake
— Run APR tests on Windows x64
— Run Subversion 1.14.x tests with this APR version on Windows x64


Thanks,
Evgeny Kotkov


Re: [VOTE] Release apr-1.7.4-rc1 as apr-1.7.4

2023-04-13 Thread Evgeny Kotkov via dev
Eric Covener  writes:

> Hi all,
>
> Please find below the proposed release tarball and signatures:
>
> https://dist.apache.org/repos/dist/dev/apr/

First time voting for an APR release, but isn't apr-1.7.4-rc1-win32-src.zip
missing from the set of artifacts?

I think that it was present during the vote for APR 1.7.3:
[[[
> svn log https://dist.apache.org/repos/dist/dev/apr -c60838 -v

Changed paths:
   M /dev/apr/Announcement1.x.html
   M /dev/apr/Announcement1.x.txt
   A /dev/apr/apr-1.7.3-rc1-win32-src.zip
   A /dev/apr/apr-1.7.3-rc1-win32-src.zip.asc
   A /dev/apr/apr-1.7.3-rc1-win32-src.zip.sha256
   A /dev/apr/apr-1.7.3-rc1.tar.bz2
   A /dev/apr/apr-1.7.3-rc1.tar.bz2.asc
   A /dev/apr/apr-1.7.3-rc1.tar.bz2.sha256
   A /dev/apr/apr-1.7.3-rc1.tar.gz
   A /dev/apr/apr-1.7.3-rc1.tar.gz.asc
   A /dev/apr/apr-1.7.3-rc1.tar.gz.sha256

1.7.3. rc1
]]]


Thanks,
Evgeny Kotkov


Re: Release APR 1.7.4?

2023-04-13 Thread Evgeny Kotkov via dev
Yann Ylavic  writes:

> Do we want to include r1909117 in 1.7.4 (to call abort() instead of
> assert() in apr_base64 when NDEBUG)?
> Sorry for the lateness, but should there be an -rc2 (or in any case..).

If I recall correctly, APR 1.7.x doesn't have apr_base64, because it is a
part of APR-util in that timeline.


Thanks,
Evgeny Kotkov


Re: Release APR 1.7.4?

2023-04-13 Thread Evgeny Kotkov via dev
Eric Covener  writes:

> I will RM but it might be a bit piecemeal between now and the weekend.
>   At the mercy of a lot of inflexible $bigco stuff.
>
> Hoping to improve a little this time by porting more of icings amazing
> httpd release work.

Awesome, thanks!


Regards,
Evgeny Kotkov


Re: svn commit: r1909094 - in /apr/apr/branches/1.8.x: ./ CHANGES

2023-04-12 Thread Evgeny Kotkov via dev
Christophe JAILLET  writes:

> +1 for me.
>
> Looking at 1.7 and 1.6 it seems it was done this way in previous releases.

Committed in https://svn.apache.org/r1909103


Thanks,
Evgeny Kotkov


Re: svn commit: r1909094 - in /apr/apr/branches/1.8.x: ./ CHANGES

2023-04-12 Thread Evgeny Kotkov via dev
Christophe JAILLET  writes:

> If we remove things that are part of previous 1.7.x releases, shouldn't
> Changes for APR 1.7.[23] be added here instead?

Hm, since these are the 1.8.x CHANGES maybe it'd be better to drop the 1.7.1,
1.7.2 and 1.7.3 entries from here and just link to branches/1.7.x/CHANGES,
to avoid maintaining two separate copies of 1.7.x CHANGES.

How does that sound?


Thanks,
Evgeny Kotkov


Release APR 1.7.4?

2023-04-12 Thread Evgeny Kotkov via dev
Hi everyone,

Unfortunately, one of the fixes in APR 1.7.3 that was authored by me has
caused a significant regression, where writing to files opened with both
APR_FOPEN_APPEND and APR_FOPEN_BUFFERED now may not properly append
the data on Windows.  The issue has been reported in [1].

Compared to the potential impact of the regression, the original fix seems
to
be of lesser concern, so I reverted the change in r1909088 [2] and
backported
it to 1.7.x in r1909095 [3].

Would it be feasible to release 1.7.4 with a fix for this regression?

(I can try to RM if needed, although in such a case it would be my first
time
 preparing an APR release.)

[1] https://lists.apache.org/thread/56gnyc3tc0orjh5mfsqo9gpq1br59b01
[2] https://svn.apache.org/r1909088
[3] https://svn.apache.org/r1909095


Thanks,
Evgeny Kotkov


Re: svn commit: r1902206 - /apr/apr/trunk/encoding/apr_base64.c

2023-03-30 Thread Evgeny Kotkov via dev
Yann Ylavic  writes:

> So possibly something like the below:
>
> Index: encoding/apr_base64.c
> ===
> --- encoding/apr_base64.c(revision 1908764)
> +++ encoding/apr_base64.c(working copy)
> @@ -20,8 +20,27 @@
>   * ugly 'len' functions, which is quite a nasty cost.
>   */
>
> -#undef NDEBUG /* always abort() on assert()ion failure */
> +/* An APR__ASSERT()ion failure will abort() with or without printing
> + * the faulting condition (and code location) depending on NDEBUG.
> + */
> +#ifndef NDEBUG  /* use assert()/system's printing before abort() mechanism */
>  #include 
> +#define APR__ASSERT(cond) assert(cond)
> +#else   /* just abort() */
> +#if APR_HAVE_STDLIB_H
> +#include 
> +#endif
> +#if HAVE___BUILTIN_EXPECT
> +#define APR__UNLIKELY(cond) __builtin_expect(!!(cond), 0)
> +#else
> +#define APR__UNLIKELY(cond) (!!(cond))
> +#endif
> +#define APR__ASSERT(cond) do { \
> +if (APR__UNLIKELY(!(cond))) { \
> +abort(); \
> +} \
> +} while (0)
> +#endif

Yes, something along these lines, although I was thinking we could maybe
get away with just adding a small helper that covers all cases, as in the
attached patch.


Thanks,
Evgeny Kotkov
Index: encoding/apr_base64.c
===
--- encoding/apr_base64.c   (revision 1908804)
+++ encoding/apr_base64.c   (working copy)
@@ -20,7 +20,6 @@
  * ugly 'len' functions, which is quite a nasty cost.
  */
 
-#undef NDEBUG /* always abort() on assert()ion failure */
 #include 
 
 #include "apr_base64.h"
@@ -28,6 +27,17 @@
 #include "apr_xlate.h"
 #endif/* APR_CHARSET_EBCDIC */
 
+#if APR_HAVE_STDLIB_H
+#include 
+#endif
+
+#define verify_condition(cond) do { \
+if (!(cond)) {  \
+assert(0 && #cond); \
+abort();\
+}   \
+} while (0)
+
 /* Above APR_BASE64_ENCODE_MAX length the encoding can't fit in an int >= 0 */
 #define APR_BASE64_ENCODE_MAX 1610612733
 
@@ -124,7 +134,7 @@ APR_DECLARE(int) apr_base64_decode_len(const char
 bufin = (const unsigned char *) bufcoded;
 while (pr2six[*(bufin++)] <= 63);
 nprbytes = (bufin - (const unsigned char *) bufcoded) - 1;
-assert(nprbytes <= APR_BASE64_DECODE_MAX);
+verify_condition(nprbytes <= APR_BASE64_DECODE_MAX);
 
 return (int)(((nprbytes + 3u) / 4u) * 3u + 1u);
 }
@@ -161,7 +171,7 @@ APR_DECLARE(int) apr_base64_decode_binary(unsigned
 bufin = (const unsigned char *) bufcoded;
 while (pr2six[*(bufin++)] <= 63);
 nprbytes = (bufin - (const unsigned char *) bufcoded) - 1;
-assert(nprbytes <= APR_BASE64_DECODE_MAX);
+verify_condition(nprbytes <= APR_BASE64_DECODE_MAX);
 nbytesdecoded = (int)(((nprbytes + 3u) / 4u) * 3u);
 
 bufout = (unsigned char *) bufplain;
@@ -206,7 +216,7 @@ static const char basis_64[] =
 
 APR_DECLARE(int) apr_base64_encode_len(int len)
 {
-assert(len >= 0 && len <= APR_BASE64_ENCODE_MAX);
+verify_condition(len >= 0 && len <= APR_BASE64_ENCODE_MAX);
 
 return ((len + 2) / 3 * 4) + 1;
 }
@@ -219,7 +229,7 @@ APR_DECLARE(int) apr_base64_encode(char *encoded,
 int i;
 char *p;
 
-assert(len >= 0 && len <= APR_BASE64_ENCODE_MAX);
+verify_condition(len >= 0 && len <= APR_BASE64_ENCODE_MAX);
 
 p = encoded;
 for (i = 0; i < len - 2; i += 3) {
@@ -258,7 +268,7 @@ APR_DECLARE(int) apr_base64_encode_binary(char *en
 int i;
 char *p;
 
-assert(len >= 0 && len <= APR_BASE64_ENCODE_MAX);
+verify_condition(len >= 0 && len <= APR_BASE64_ENCODE_MAX);
 
 p = encoded;
 for (i = 0; i < len - 2; i += 3) {
@@ -292,7 +302,7 @@ APR_DECLARE(char *) apr_pbase64_encode(apr_pool_t
 char *encoded;
 apr_size_t len = strlen(string);
 
-assert(len <= (apr_size_t)APR_BASE64_ENCODE_MAX);
+verify_condition(len <= (apr_size_t)APR_BASE64_ENCODE_MAX);
 encoded = (char *) apr_palloc(p, apr_base64_encode_len((int)len));
 apr_base64_encode(encoded, string, (int)len);
 


Re: svn commit: r1902206 - /apr/apr/trunk/encoding/apr_base64.c

2023-03-29 Thread Evgeny Kotkov via dev
Yann Ylavic  writes:

> +#undef NDEBUG /* always abort() on assert()ion failure */
> +#include 

Sorry for being late to the party, but I think there are a few problems with
the "#undef NDEBUG" line:

1) The debug implementation of an assert() may print a diagnostic message,
for example to stderr.  A caller of the library function may not be ready for
this to happen when using a non-debug version of the library.

2) The actual destination of the message seems to be implementation-defined.
For example, in Windows-based applications this may show a message box [1],
which is probably even more unexpected for the user of the library.

3) Undefining NDEBUG before other headers may silently cause unexpected
effects if any of those headers make some decisions based on the NDEBUG value,
which isn't an entirely unreasonable thing to expect.

So, depending on whether we want the checks to soft- or hard-fault, maybe we
could either remove the "#undef NDEBUG" line, or switch to a custom check that
explicitly calls an abort()?

[1] 
https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/assert-macro-assert-wassert#remarks


Thanks,
Evgeny Kotkov


Re: svn commit: r1902375 - in /apr/apr/branches/1.8.x: ./ CHANGES file_io/win32/readwrite.c test/testfile.c

2022-07-01 Thread Evgeny Kotkov via dev
Yann Ylavic  writes:

> Oh, and the loop seems to have been removed later in r1828509
> "apr_file_read: Optimize large reads from buffered files on Windows"
> (backported to 2.4.x in r1902379 too).
> So Windows is doing the right thing now (not unix), right?

Yes, AFAICT unix still uses the original approach.

> Sorry for the noise, and thanks for the fix/info :)


Thanks,
Evgeny Kotkov


Re: svn commit: r1902378 - in /apr/apr/branches/1.8.x: ./ CHANGES file_io/win32/readwrite.c test/testfile.c

2022-07-01 Thread Evgeny Kotkov via dev
Yann Ylavic  writes:

> But the short-write argument stands, if Windows never short-writes
> then the implicit full-write is unnecessary, but if it does
> short-write then apr_file_write() has to return it too (for the user
> to know), so in both cases the loop has to stop when written <
> to_write (and probably return success if written > 0).
> Users that don't expect short-writes should use apr_file_write_full().

As far as I recall, I had an intent of not changing the behavior more than
necessary, as the original code was always making a full write.

I agree that we could potentially allow for short writes when performing
the passthrough part of the write that makes its way around the buffer.

But all in all, I think that we might want to keep the current approach,
because:

— This won't change the actual behavior for Win32 files from a practical
  standpoint, because synchronous WriteFile() guarantees a full write
  for them.

— There's a tricky case where we need to handle a large write with
  len > DWORD_MAX.  I think that we might want to not handle it with a
  short write, because if we do so, we're going to have an actually
  reproducible short write for files (i.e., different behavior) in the rare
  edge case.

— Flushing the buffer requires a full write, so at least a part of the
  buffered apr_file_write() is still going to do a full write.


Regards,
Evgeny Kotkov


Re: svn commit: r1902375 - in /apr/apr/branches/1.8.x: ./ CHANGES file_io/win32/readwrite.c test/testfile.c

2022-07-01 Thread Evgeny Kotkov via dev
Yann Ylavic  writes:

> Why would buffered reads always be full reads (i.e. until the given
> size of EOF)?
>
> For apr_file_read() I don't think this is expected, users might want
> read() to return what is there when it's there (i.e. they handle short
> reads by themselves), so implicit full reads will force them to use
> small buffers artificially and they'll lose in throughput. On the
> other hand, users that want full reads can do that already/currently
> in their own code (by calling read() multiple times).
>
> For apr_file_gets() this is the same issue, why when some read returns
> the \n would we need to continue reading to fill the buffer? In a pipe
> scenario the peer would apr_file_write("Hello\n") and the consumer's
> apr_file_read() would not return that to the user immediately?
>
> This has nothing to do with buffered vs non-buffered IMHO, there are
> apr_file_{read,write}_full() already for that.

One comment I have here is that this commit does not change the existing
behavior of apr_file_read() for buffered files.  The read_buffered() helper
isn't new by itself, as it has been merely factored out from apr_file_read().

So, doing full reads for buffered files has been the current behavior
for both unix and win32 and for a long time, I think.  For example, see
unix/readwrite.c:file_read_buffered().  This applies to the unix version
of apr_file_gets() as well.


Regards,
Evgeny Kotkov


Re: svn commit: r1896510 - in /apr/apr/branches/1.7.x: ./ file_io/win32/ include/arch/unix/ include/arch/win32/ network_io/os2/ poll/os2/ poll/unix/

2022-02-08 Thread Evgeny Kotkov
Ivan Zhakov  writes:

> This part is now in the following branch:
> https://svn.ostyserver.net/svn/asf/apr/apr/branches/win32-pollset-wakeup-no-file-socket-emulation
>
> What do you think?
>
> It would be great if someone could take a look on the implementation from
> the *nix perspective.
> After that, I propose to merge the branch into trunk.

In case this helps, I have tested the branch on Ubuntu x64 and it seems
to compile and pass the tests.

(While here, I noticed that APR trunk doesn't build on Windows;
 have posted on that separately.)


Thanks,
Evgeny Kotkov


Re: svn commit: r1897207 - in /apr/apr/trunk: include/apr_thread_proc.h threadproc/beos/thread.c threadproc/netware/thread.c threadproc/os2/thread.c threadproc/unix/thread.c threadproc/win32/thread.c

2022-02-08 Thread Evgeny Kotkov
Yann Ylavic  writes:

> +APR_DECLARE(apr_status_t) apr_thread_current_create(apr_thread_t **current,
> +apr_threadattr_t *attr,
> +apr_pool_t *pool)
> +{
> +apr_status_t stat;
> +
> +*current = apr_thread_current();
> +if (*current) {
> +return APR_EEXIST;
> +}
> +
> +stat = alloc_thread(current, attr, NULL, NULL, pool);
> +if (stat != APR_SUCCESS) {
> +return stat;
> +}
> +
> +if (!(attr && attr->detach)) {
> +(*new)->td = apr_os_thread_current();
> +}

Hi Yann,

It looks like this hunk doesn't compile on Windows:

  …\threadproc\win32\thread.c(193): error C2065: 'new': undeclared identifier
  …\threadproc\win32\thread.c(193): error C2100: illegal indirection
  …\threadproc\win32\thread.c(193): error C2223: left of '->td' must point
  to struct/union


Thanks,
Evgeny Kotkov


Re: svn commit: r1833786 - /apr/apr/branches/1.6.x/STATUS

2018-06-20 Thread Evgeny Kotkov
William A Rowe Jr  writes:

> Confusing... which patch is missing?
>
> http://svn.apache.org/viewvc?view=revision=1808457
> http://svn.apache.org/viewvc?view=revision=1829962
>
> It should not be possible at this point to need 1.x specific code
> that wouldn't correspond to a trunk commit.

I think that r1829962 should be treated as a follow-up to r1808457
(where I missed one calling site of apr_thread_mutex_unlock() in buffer.c).

In other words, they should either be backported together, or not backported
at all.  Since this change is a relatively minor optimization (not a fix),
I would think that it might make sense to keep it just in trunk.


Thanks,
Evgeny Kotkov


Re: svn commit: r1828509 - in /apr/apr/trunk: CHANGES file_io/win32/readwrite.c test/testfile.c

2018-04-09 Thread Evgeny Kotkov
William A Rowe Jr <wr...@rowe-clan.net> writes:

> Awesome!
>
> This looks entirely appropriate for 1.7 and even a 1.6.4 release,
> since the changes are all "under the hood".

Thanks!

I will try to backport this change sometime later this week.


Regards,
Evgeny Kotkov


Re: [PATCH] Create and use file mutex only for files opened with APR_FOPEN_XTHREAD

2017-09-15 Thread Evgeny Kotkov
Evgeny Kotkov <evgeny.kot...@visualsvn.com> writes:

> Currently, there are some cases in the Windows implementation of the file
> I/O that cause the unnecessary creation and acquisitions of the file mutex.
> These cases include handling O_APPEND-style writes and the implementation
> of the apr_file_buffer_set().
>
> Creating and acquiring the file mutex is only required for files opened
> with the APR_FOPEN_XTHREAD flag.  Otherwise, concurrent operations
> on the same apr_file_t instance should be serialized by the user, and
> the mutex is not required.
>
> This patch tweaks the implementation to only create and acquire/release
> the mutex for files opened with APR_FOPEN_XTHREAD.  The log message
> is included in the beginning of the patch file.

Committed in https://svn.apache.org/r1808457


Regards,
Evgeny Kotkov


Re: [PATCH] Don't seek to the end when opening files with APR_FOPEN_APPEND

2017-09-15 Thread Evgeny Kotkov
Evgeny Kotkov <evgeny.kot...@visualsvn.com> writes:

> Currently, the Windows implementation of APR_FOPEN_APPEND flag performs
> a seek to the file's end when opening it.
>
> Apparently, this is a leftover from the very first version of handling file
> appends (https://svn.apache.org/r59449) that performed a single seek to
> the file's end when opening it and did not support proper atomic appends
> with multiple process or threads writing to the same file.
>
> Since then, such atomic appends have been implemented, but the seek
> was not removed.  It can cause unexpected behavior when reading from
> a file opened with APR_FOPEN_APPEND, assuming no writes happened
> to the file.  In this case, as there have been no writes, the file offset
> should not be repositioned and reading should start from the beginning of
> the file.  However, due to the unwanted seek during open, the actual reading
> would instead start from the file's end and cause an unexpected EOF.
>
> The attached patch fixes the issue and adds a test for it.  The log message
> is included in the beginning of the patch file.

Committed in https://svn.apache.org/r1808456


Regards,
Evgeny Kotkov


Re: svn commit: r1806603 - /apr/apr/trunk/file_io/win32/readwrite.c

2017-08-30 Thread Evgeny Kotkov
Bert Huijben <b...@qqmail.nl> writes:

>>  rv = WriteFile(thefile->filehand, buf, (DWORD)*nbytes, ,
>> thefile->pOverlapped);
>> +if (rv == APR_SUCCESS && thefile->pOverlapped) {
>> +thefile->filePtr += *nbytes;
>> +}
>
> The result of WriteFile should not be checked against APR_SUCCESS, as that
> is not a documented Windows result code. The right constant evaluating to
> 0 should be used.

Unfortunately, I think that I have missed how the return value of WriteFile()
was being checked in the last part of apr_file_write(), and both the r1806592
and r1806603 changesets are wrong and change the behavior of the code.

As these changesets were meant to be refactorings that make the code
clearer, without altering its behavior, I think that they should be reverted
for now.

(I will prepare an alternative patch with these simplifications that doesn't
change the behavior of the code.)


Sorry for that,
Evgeny Kotkov


[PATCH] Don't seek to the end when opening files with APR_FOPEN_APPEND

2017-08-29 Thread Evgeny Kotkov
Hi everyone,

Currently, the Windows implementation of APR_FOPEN_APPEND flag performs
a seek to the file's end when opening it.

Apparently, this is a leftover from the very first version of handling file
appends (https://svn.apache.org/r59449) that performed a single seek to
the file's end when opening it and did not support proper atomic appends
with multiple process or threads writing to the same file.

Since then, such atomic appends have been implemented, but the seek
was not removed.  It can cause unexpected behavior when reading from
a file opened with APR_FOPEN_APPEND, assuming no writes happened
to the file.  In this case, as there have been no writes, the file offset
should not be repositioned and reading should start from the beginning of
the file.  However, due to the unwanted seek during open, the actual reading
would instead start from the file's end and cause an unexpected EOF.

The attached patch fixes the issue and adds a test for it.  The log message
is included in the beginning of the patch file.


Thanks,
Evgeny Kotkov
Win32: Don't seek to the end when opening files with APR_FOPEN_APPEND.

Apparently, this is a leftover from the very first version of handling file
appends (https://svn.apache.org/r59449) that performed a single seek to
the file's end when opening it and did not support proper atomic appends
with multiple process or threads writing to the same file.

Since then, such atomic appends have been implemented, but the seek
was not removed.  It can cause unexpected behavior when reading from
a file opened with APR_FOPEN_APPEND, assuming no writes happened
to the file.  In this case, as there have been no writes, the file offset
should not be repositioned and reading should start from the beginning of
the file.  However, due to the unwanted seek during open, the actual reading
would instead start from the file's end and cause an unexpected EOF.

* file_io/win32/open.c
  (apr_file_open): Don't seek to the file's end when the file is
   opened with APR_FOPEN_APPEND.

* test/testfile.c
  (test_append_read): New test.
  (testfile): Run the new test.

Patch by: Evgeny Kotkov 

Index: file_io/win32/open.c
===
--- file_io/win32/open.c(revision 1806645)
+++ file_io/win32/open.c(working copy)
@@ -445,7 +445,6 @@ APR_DECLARE(apr_status_t) apr_file_open(apr_file_t
 
 if (flag & APR_FOPEN_APPEND) {
 (*new)->append = 1;
-SetFilePointer((*new)->filehand, 0, NULL, FILE_END);
 }
 if (flag & APR_FOPEN_BUFFERED) {
 (*new)->buffered = 1;
Index: test/testfile.c
===
--- test/testfile.c (revision 1806645)
+++ test/testfile.c (working copy)
@@ -1793,6 +1793,56 @@ static void test_append_locked(abts_case *tc, void
 apr_file_remove(fname, p);
 }
 
+static void test_append_read(abts_case *tc, void *data)
+{
+apr_status_t rv;
+apr_file_t *f;
+const char *fname = "data/testappend_read.dat";
+apr_off_t offset;
+char buf[64];
+
+apr_file_remove(fname, p);
+
+/* Create file with contents. */
+rv = apr_file_open(, fname, APR_FOPEN_WRITE | APR_FOPEN_CREATE,
+   APR_FPROT_OS_DEFAULT, p);
+APR_ASSERT_SUCCESS(tc, "create file", rv);
+
+rv = apr_file_puts("abc", f);
+APR_ASSERT_SUCCESS(tc, "write to file", rv);
+apr_file_close(f);
+
+/* Re-open it with APR_FOPEN_APPEND. */
+rv = apr_file_open(, fname,
+   APR_FOPEN_READ | APR_FOPEN_WRITE | APR_FOPEN_APPEND,
+   APR_FPROT_OS_DEFAULT, p);
+APR_ASSERT_SUCCESS(tc, "open file", rv);
+
+/* Test the initial file offset.  Even though we used APR_FOPEN_APPEND,
+ * the offset should be kept in the beginning of the file until the
+ * first append.  (Previously, the Windows implementation performed
+ * an erroneous seek to the file's end right after opening it.)
+ */
+offset = 0;
+rv = apr_file_seek(f, APR_CUR, );
+APR_ASSERT_SUCCESS(tc, "get file offset", rv);
+ABTS_INT_EQUAL(tc, 0, (int)offset);
+
+/* Test reading from the file. */
+rv = apr_file_gets(buf, sizeof(buf), f);
+APR_ASSERT_SUCCESS(tc, "read from file", rv);
+ABTS_STR_EQUAL(tc, "abc", buf);
+
+/* Test the file offset after reading. */
+offset = 0;
+rv = apr_file_seek(f, APR_CUR, );
+APR_ASSERT_SUCCESS(tc, "get file offset", rv);
+ABTS_INT_EQUAL(tc, 3, (int)offset);
+
+apr_file_close(f);
+apr_file_remove(fname, p);
+}
+
 abts_suite *testfile(abts_suite *suite)
 {
 suite = ADD_SUITE(suite)
@@ -1848,6 +1898,7 @@ abts_suite *testfile(abts_suite *suite)
 abts_run_test(suite, test_write_buffered_spanning_over_bufsize, NULL);
 abts_run_test(suite, test_atomic_append, NULL);
 abts_run_test(suite, test_append_locked, NULL);
+abts_run_test(suite, test_append_read, NULL);
 
 return suite;
 }


[PATCH] Create and use file mutex only for files opened with APR_FOPEN_XTHREAD

2017-08-29 Thread Evgeny Kotkov
Hi everyone,

Currently, there are some cases in the Windows implementation of the file
I/O that cause the unnecessary creation and acquisitions of the file mutex.
These cases include handling O_APPEND-style writes and the implementation
of the apr_file_buffer_set().

Creating and acquiring the file mutex is only required for files opened
with the APR_FOPEN_XTHREAD flag.  Otherwise, concurrent operations
on the same apr_file_t instance should be serialized by the user, and
the mutex is not required.

This patch tweaks the implementation to only create and acquire/release
the mutex for files opened with APR_FOPEN_XTHREAD.  The log message
is included in the beginning of the patch file.


Thanks,
Evgeny Kotkov
Win32: Create and use file mutex only for files opened with APR_FOPEN_XTHREAD.

There are some cases in the Windows implementation of the file I/O that
cause the unnecessary creation and acquisitions of the file mutex.
These cases include handling O_APPEND-style writes and the implementation
of the apr_file_buffer_set().

Creating and acquiring the file mutex is only required for files opened
with the APR_FOPEN_XTHREAD flag.  Otherwise, concurrent operations on
the same apr_file_t instance should be serialized by the user, and
the mutex is not required.

This patch tweaks the implementation to only create and acquire/release
the mutex for files opened with APR_FOPEN_XTHREAD.

* file_io/win32/open.c
  (apr_file_open, apr_os_file_put): Create the file mutex only
   with APR_FOPEN_XTHREAD.

* file_io/win32/buffer.c
  (apr_file_buffer_set): Use the file mutex only with APR_FOPEN_XTHREAD.

* file_io/win32/readwrite.c
  (apr_file_write): Use the file mutex only with APR_FOPEN_XTHREAD.

Patch by: Evgeny Kotkov 

Index: file_io/win32/buffer.c
===
--- file_io/win32/buffer.c  (revision 1806645)
+++ file_io/win32/buffer.c  (working copy)
@@ -23,7 +23,9 @@ APR_DECLARE(apr_status_t) apr_file_buffer_set(apr_
 {
 apr_status_t rv;
 
-apr_thread_mutex_lock(file->mutex);
+if (file->flags & APR_FOPEN_XTHREAD) {
+apr_thread_mutex_lock(file->mutex);
+}
  
 if(file->buffered) {
 /* Flush the existing buffer */
@@ -48,7 +50,9 @@ APR_DECLARE(apr_status_t) apr_file_buffer_set(apr_
 file->buffered = 0;
 }
 
-apr_thread_mutex_unlock(file->mutex);
+if (file->flags & APR_FOPEN_XTHREAD) {
+apr_thread_mutex_unlock(file->mutex);
+}
 
 return APR_SUCCESS;
 }
Index: file_io/win32/open.c
===
--- file_io/win32/open.c(revision 1806645)
+++ file_io/win32/open.c(working copy)
@@ -452,8 +452,8 @@ APR_DECLARE(apr_status_t) apr_file_open(apr_file_t
 (*new)->buffer = apr_palloc(pool, APR_FILE_DEFAULT_BUFSIZE);
 (*new)->bufsize = APR_FILE_DEFAULT_BUFSIZE;
 }
-/* Need the mutex to handled buffered and O_APPEND style file i/o */
-if ((*new)->buffered || (*new)->append) {
+/* Need the mutex to share an apr_file_t across multiple threads */
+if (flag & APR_FOPEN_XTHREAD) {
 rv = apr_thread_mutex_create(&(*new)->mutex, 
  APR_THREAD_MUTEX_DEFAULT, pool);
 if (rv) {
@@ -645,8 +645,7 @@ APR_DECLARE(apr_status_t) apr_os_file_put(apr_file
 (*file)->buffer = apr_palloc(pool, APR_FILE_DEFAULT_BUFSIZE);
 (*file)->bufsize = APR_FILE_DEFAULT_BUFSIZE;
 }
-
-if ((*file)->append || (*file)->buffered) {
+if (flags & APR_FOPEN_XTHREAD) {
 apr_status_t rv;
 rv = apr_thread_mutex_create(&(*file)->mutex, 
  APR_THREAD_MUTEX_DEFAULT, pool);
Index: file_io/win32/readwrite.c
===
--- file_io/win32/readwrite.c   (revision 1806645)
+++ file_io/win32/readwrite.c   (working copy)
@@ -412,20 +412,26 @@ APR_DECLARE(apr_status_t) apr_file_write(apr_file_
 if (thefile->append) {
 apr_off_t offset = 0;
 
-/* apr_file_lock will mutex the file across processes.
- * The call to apr_thread_mutex_lock is added to avoid
- * a race condition between LockFile and WriteFile 
- * that occasionally leads to deadlocked threads.
- */
-apr_thread_mutex_lock(thefile->mutex);
+if (thefile->flags & APR_FOPEN_XTHREAD) {
+/* apr_file_lock will mutex the file across processes.
+ * The call to apr_thread_mutex_lock is added to avoid
+ * a race condition between LockFile and WriteFile
+ * that occasionally leads to deadlocked threads.
+ */
+apr_thread_mutex_lock(thefile->mutex);
+  

[PATCH] Fix a deadlock when appending to locked files on Windows (PR50058)

2017-08-28 Thread Evgeny Kotkov
Hi everyone,

Currently, appending data to a file opened with APR_FOPEN_APPEND
that has been locked by apr_file_lock() will cause a deadlock on Windows.

[See PR50058, https://bz.apache.org/bugzilla/show_bug.cgi?id=50058]

This issue happens because atomic O_APPEND-style appends on Windows
are implemented using file locks.  An append happens while holding the
file lock acquired with LockFile(), which is required to avoid a race
condition between seeking to the end of file and writing data.  The
race is possible when multiple threads or processes are appending
data to the same file.

This approach causes a deadlock if the file has been previously locked
with apr_file_lock().  (Note that it's perfectly legit to lock the file or
its portion and perform the append after that.)

Apart from this, using file locks for file appends impacts their speed and
robustness.  There is an overhead associated with locking the file, especially
if the file is not local.  The robustness is affected, because other writes
to the same file may fail due to it being locked.  Also, if a process is
terminated in the middle of the append operation, it might take some time
for the OS to release the file lock.  During this time, the file would be
inaccessible to other processes.  This may affect applications such as
httpd (with a multi-process MPM) that use APR_FOPEN_APPEND files for
logging.

This patch series fixes the issue by switching to the documented way to
atomically append data with a single WriteFile() call.  It requires passing
special OVERLAPPED.Offset and OffsetHigh values (0x).  On the
ZwWriteFile() layer, this maps to a FILE_WRITE_TO_END_OF_FILE constant
that instructs the OS (the corresponding file system driver) to write data
to the file's end.

Note that this approach is only used for files opened for synchronous I/O
because in this case the I/O Manager maintains the current file position.
Otherwise, the file offset returned or changed by the SetFilePointer() API is
not guaranteed to be valid and that could, for instance, break apr_file_seek()
calls after appending data.  Sadly, if a file is opened for asynchronous I/O,
this call to WriteFile() doesn't update the OVERLAPPED.Offset member to
reflect the actual offset used when appending the data (which we could
then use to make seeking and other operations involving filePtr work).
Therefore, when appending to files opened for asynchronous I/O, we still
use the old LockFile + SetFilePointer + WriteFile approach.

Additional details on this can be found in:

  https://msdn.microsoft.com/en-us/library/windows/desktop/aa365747
  https://msdn.microsoft.com/en-us/library/windows/hardware/ff567121


The implementation is split into four dependent patches.  The first three
patches are minor refactorings to the apr_file_write() function and lay the
necessary groundwork for the core change.  The fourth patch is the core
change that fixes the issue in the described way and also includes the
tests.

The log messages are included in the beginning of each patch file.


Thanks,
Evgeny Kotkov
Minor refactoring of the Win32 file write code.

* file_io/win32/readwrite.c
  (apr_file_write): Reuse the existing 'rv' variable to store the
   status code.  Reduce the scope of the 'offset' variable.

Patch by: Evgeny Kotkov 

Index: file_io/win32/readwrite.c
===
--- file_io/win32/readwrite.c   (revision 1806361)
+++ file_io/win32/readwrite.c   (working copy)
@@ -372,9 +372,9 @@ APR_DECLARE(apr_status_t) apr_file_write(apr_file_
 return rv;
 } else {
 if (!thefile->pipe) {
-apr_off_t offset = 0;
-apr_status_t rc;
 if (thefile->append) {
+apr_off_t offset = 0;
+
 /* apr_file_lock will mutex the file across processes.
  * The call to apr_thread_mutex_lock is added to avoid
  * a race condition between LockFile and WriteFile 
@@ -381,15 +381,15 @@ APR_DECLARE(apr_status_t) apr_file_write(apr_file_
  * that occasionally leads to deadlocked threads.
  */
 apr_thread_mutex_lock(thefile->mutex);
-rc = apr_file_lock(thefile, APR_FLOCK_EXCLUSIVE);
-if (rc != APR_SUCCESS) {
+rv = apr_file_lock(thefile, APR_FLOCK_EXCLUSIVE);
+if (rv != APR_SUCCESS) {
 apr_thread_mutex_unlock(thefile->mutex);
-return rc;
+return rv;
 }
-rc = apr_file_seek(thefile, APR_END, );
-if (rc != APR_SUCCESS) {
+rv = apr_file_seek(thefile, APR_END, );
+if (rv != APR_SUCCESS) {
 apr_thread_mutex_unlock(thefile->mutex);
-return rc;
+return rv;
 }
 }
 if (thefile->pOverlapped) {

Re: [PATCH] Reduce the amount of WriteFile() syscalls when writing to buffered files

2017-08-23 Thread Evgeny Kotkov
Evgeny Kotkov <evgeny.kot...@visualsvn.com> writes:

> In the meanwhile, apparently, there is an oversight in the core V1 patch
> (3-reduce-syscalls-for-buffered-writes-v1.patch.txt):
>
> If the buffer is not empty, and the caller issues a write with a chunk
> that slightly exceeds the buffer size, for example, 4100 bytes, it would
> result in flushing the buffer _and_ writing the remaining couple of bytes
> with WriteFile().  An appropriate thing to do here would be to flush the
> buffer, but put the few remaining bytes into the buffer, instead of writing
> them to disk and thus making an unnecessary syscall.
>
> With all this in mind, I will prepare the V2 version of the core patch.

I have attached the V2 version of the core patch.

To solve the aforementioned oversight in the V1 patch, I implemented the
optimization in a slightly different manner, by keeping the existing loop
but also handling a condition where we can write the remaining data with
a single WriteFile() call.  Apart from this, the V2 patch also includes an
additional test, test_small_and_large_writes_buffered().

Speaking of the discussed idea of adjusting the strategy to avoid a memcpy()
with the write pattern like

WriteFile(13)
WriteFile(86267)
...

instead of

WriteFile(4096)
WriteFile(82185)
...

I preferred to keep the latter approach which keeps the minimum size of
the WriteFile() chunk equal to 4096, for the following reasons:

  - My benchmarks do not show that the alternative pattern that also avoids
a memcpy() results in a quantifiable performance improvement.

  - The existing code had a property that all WriteFile() calls, except
for maybe the last one, were performed in chunks with sizes that are
never less than 4096.  Although I can't reproduce this in my environment,
it could be that introducing a possibility of writing in smaller chunks
would result in the decreased performance with specific hardware, non-
file handles or in situations when the OS write caching is disabled.
Therefore, currently, I think that it would be better to keep this
existing property.

  - Probably, implementing the first approach would result in a bit more
complex code, as I think that it would require introducing an additional
if-else code path.

The log message is included in the beginning of the patch file.


Thanks,
Evgeny Kotkov
Win32: Improve apr_file_write() performance on buffered files by reducing
the amount of WriteFile() calls for large writes.

Previously, writing has been implemented with a loop that keeps copying the
data to the internal 4KB buffer and writing this buffer to disk by calling
WriteFile(4096).  This patch reduces the amount of syscalls for large writes
by performing them with a single syscall, if possible.  If the buffer is
not empty at the moment when the large write occurs, it is first filled
up to its 4KB capacity, flushed, and the remaining part of the data is
written with a single syscall.

* file_io/win32/readwrite.c
  (write_buffered): Within the write loop, check if we have a situation
   with an empty buffer and a large chunk pending to be written.  In this
   case, bypass the buffering and write the remaining chunk with a single
   syscall.  Return an appropriate number of written bytes to satisfy
   the apr_file_write() function contract.
  (apr_file_write): Adjust call to write_buffered().

* test/testfile.c
  (test_large_write_buffered,
   test_two_large_writes_buffered,
   test_small_and_large_writes_buffered,
   test_write_buffered_spanning_over_bufsize): New tests.
  (testfile): Run the new tests.

Patch by: Evgeny Kotkov 

Index: file_io/win32/readwrite.c
===
--- file_io/win32/readwrite.c   (revision 1805607)
+++ file_io/win32/readwrite.c   (working copy)
@@ -290,9 +290,8 @@ static apr_status_t write_helper(HANDLE filehand,
 }
 
 static apr_status_t write_buffered(apr_file_t *thefile, const char *buf,
-   apr_size_t len)
+   apr_size_t len, apr_size_t *pwritten)
 {
-apr_size_t blocksize;
 apr_status_t rv;
 
 if (thefile->direction == 0) {
@@ -306,20 +305,41 @@ static apr_status_t write_buffered(apr_file_t *the
 thefile->direction = 1;
 }
 
-rv = 0;
-while (rv == 0 && len > 0) {
-if (thefile->bufpos == thefile->bufsize)   /* write buffer is full */
+*pwritten = 0;
+
+while (len > 0) {
+if (thefile->bufpos == thefile->bufsize) { /* write buffer is full */
 rv = apr_file_flush(thefile);
+if (rv) {
+return rv;
+}
+}
+/* If our buffer is empty, and we cannot fit the remaining chunk
+ * into it, write the chunk with a single syscall and return.
+ */
+if (thefile->bufpos == 0 && len > thef

Re: [PATCH] Reduce the amount of WriteFile() syscalls when writing to buffered files

2017-08-22 Thread Evgeny Kotkov
Markus Schaber <m.scha...@codesys.com> writes:

> Hi, Evgeny,
>
> Great work, IMHO.

Thank you :-)

>> This patch series reduces the amount of syscalls in such situations by
>> performing a single WriteFile() call without any buffering, if possible.
>> If some data is already buffered, then the buffer is first filled, flushed
>> and the remaining part of the data is written with a single WriteFile().
>
> Why first refilling the buffer, instead of flushing the partial buffer
> content, and then the original data to write?
>
> From my first glance, it looks like the number of syscalls should be
> the same, and we could skip the mem copy?

My original intention here was to avoid issuing small and non-4K-sized
writes, which could happen if we don't refill the buffer.

For instance, in the example above doing so would result in

WriteFile(13)
WriteFile(86267)
WriteFile(13)
WriteFile(86400)
...

instead of

WriteFile(4096)
WriteFile(82185)
WriteFile(4096)
WriteFile(82317)
...

But I should also say that I ran a couple of tests and I can't see any
measurable performance difference between these two approaches, so it
might indeed make sense to switch to the first of them and avoid a memcpy().

In the meanwhile, apparently, there is an oversight in the core V1 patch
(3-reduce-syscalls-for-buffered-writes-v1.patch.txt):

If the buffer is not empty, and the caller issues a write with a chunk
that slightly exceeds the buffer size, for example, 4100 bytes, it would
result in flushing the buffer _and_ writing the remaining couple of bytes
with WriteFile().  An appropriate thing to do here would be to flush the
buffer, but put the few remaining bytes into the buffer, instead of writing
them to disk and thus making an unnecessary syscall.

With all this in mind, I will prepare the V2 version of the core patch.


Thanks,
Evgeny Kotkov


[PATCH] Reduce the amount of WriteFile() syscalls when writing to buffered files

2017-08-21 Thread Evgeny Kotkov
Hi everyone,

Currently, apr_file_write() can cause an excessive amount of syscalls
for buffered files on Windows in some situations.  This happens because
for buffered files, writing is implemented with a loop that keeps copying
the data to the internal 4KB buffer and writing the contents of this
buffer to disk with WriteFile().  In other words, performing a 100 KB
write currently results in 25 consecutive WriteFile(4096) syscalls.

This patch series reduces the amount of syscalls in such situations by
performing a single WriteFile() call without any buffering, if possible.
If some data is already buffered, then the buffer is first filled, flushed
and the remaining part of the data is written with a single WriteFile().

My measurements indicate that writing in larger chunks and avoiding
such syscalls can save a certain part of the CPU time.  For example,
"svn import" does both small and large buffered writes with the following
pattern:

write: nbytes = 9
write: nbytes = 5
write: nbytes = 86267
write: nbytes = 9
write: nbytes = 5
write: nbytes = 86413
write: nbytes = 9
write: nbytes = 5
write: nbytes = 86415
write: nbytes = 9
write: nbytes = 5
write: nbytes = 67680
...

When I test "svn import" for a large file before and after this patch, I
see that

  - the amount of syscalls decreases from 199,403 to 17,061 and

  - the overall CPU time decreases from 7.28 s to 6.68 s.  (I think that's
a lot, considering that "svn import" does a many other CPU-intensive
operations.)

The implementation is split into three dependent patches.  The first two
patches lay the necessary groundwork by factoring out a couple of helper
functions.  The third patch is the core change that implements the
described optimization.

The log messages are included in the beginning of each patch file.


Thanks,
Evgeny Kotkov
Factor out a helper function that adapts WriteFile() to apr_size_t
instead of DWORD.

* file_io/win32/readwrite.c
  (write_helper): New helper function, factored out from ...
  (apr_file_flush): ...here.

Patch by: Evgeny Kotkov 

Index: file_io/win32/readwrite.c
===
--- file_io/win32/readwrite.c   (revision 1805607)
+++ file_io/win32/readwrite.c   (working copy)
@@ -257,6 +257,38 @@ APR_DECLARE(apr_status_t) apr_file_rotating_manual
 return APR_ENOTIMPL;
 }
 
+/* Helper function that adapts WriteFile() to apr_size_t instead
+ * of DWORD. */
+static apr_status_t write_helper(HANDLE filehand, const char *buf,
+ apr_size_t len, apr_size_t *pwritten)
+{
+apr_size_t remaining = len;
+
+*pwritten = 0;
+do {
+DWORD to_write;
+DWORD written;
+
+if (remaining > APR_DWORD_MAX) {
+to_write = APR_DWORD_MAX;
+}
+else {
+to_write = (DWORD)remaining;
+}
+
+if (!WriteFile(filehand, buf, to_write, , NULL)) {
+*pwritten += written;
+return apr_get_os_error();
+}
+
+*pwritten += written;
+remaining -= written;
+buf += written;
+} while (remaining);
+
+return APR_SUCCESS;
+}
+
 APR_DECLARE(apr_status_t) apr_file_write(apr_file_t *thefile, const void *buf, 
apr_size_t *nbytes)
 {
 apr_status_t rv;
@@ -576,35 +608,15 @@ APR_DECLARE(apr_status_t) apr_file_gets(char *str,
 APR_DECLARE(apr_status_t) apr_file_flush(apr_file_t *thefile)
 {
 if (thefile->buffered) {
-DWORD numbytes, written = 0;
 apr_status_t rc = 0;
-char *buffer;
-apr_size_t bytesleft;
 
 if (thefile->direction == 1 && thefile->bufpos) {
-buffer = thefile->buffer;
-bytesleft = thefile->bufpos;   
+apr_size_t written;
 
-do {
-if (bytesleft > APR_DWORD_MAX) {
-numbytes = APR_DWORD_MAX;
-}
-else {
-numbytes = (DWORD)bytesleft;
-}
+rc = write_helper(thefile->filehand, thefile->buffer,
+  thefile->bufpos, );
+thefile->filePtr += written;
 
-if (!WriteFile(thefile->filehand, buffer, numbytes, , 
NULL)) {
-rc = apr_get_os_error();
-thefile->filePtr += written;
-break;
-}
-
-thefile->filePtr += written;
-bytesleft -= written;
-buffer += written;
-
-} while (bytesleft > 0);
-
 if (rc == 0)
 thefile->bufpos = 0;
 }
Factor out a helper function that contains the implementation of writing
to buffered files.

* file_io/win32/readwrite.c
  (write_buffered): New helper function, factored out from ...
  

Re: [PATCH] Allow larger apr_socket_listen() backlog queue lengths on Windows 8+

2017-07-11 Thread Evgeny Kotkov
Evgeny Kotkov <evgeny.kot...@visualsvn.com> writes:

> The attached patch starts using SOMAXCONN_HINT() in the Win32
> implementation of the apr_socket_listen() function if this feature is
> supported by the underlying version of Windows.

With a bit more thought on this, here is the V2 version of this patch that
doesn't require having a new Windows SDK to get the new behavior.

This is achieved by optionally borrowing the definition of SOMAXCONN_HINT()
from an appropriate version of the SDK.


Regards,
Evgeny Kotkov
apr_socket_listen(): Allow larger backlog queue lengths on Windows 8+.

Starting with Windows 8, the socket listen() function accepts a special
SOMAXCONN_HINT(N) argument that allows making the backlog queue
length larger than the otherwise predefined limit of around 200:

  https://msdn.microsoft.com/en-us/library/windows/desktop/ms739168
  
https://blogs.msdn.microsoft.com/winsdk/2015/06/01/winsocks-listen-backlog-offers-more-flexibility-in-windows-8/

Having a larger listen backlog can be used for certain high performance
applications that need to handle lots of incoming connections.  One
example would be the httpd server with it's "ListenBacklog" directive
where setting it to a larger value currently allows serving more concurrent
connections on Windows with mpm_winnt.

* include/arch/win32/apr_arch_misc.h
  (enum apr_oslevel_e): Add APR_WIN_8.

* misc/win32/misc.c
  (apr_get_oslevel): Determine whether we are running on Windows 7 or
   on Windows 8+.

* network_io/win32/sockets.c
  (SOMAXCONN_HINT): Define this macro in case we are building against
   an older version of Windows SDK.
  (apr_socket_listen): Use SOMAXCONN_HINT() for the backlog queue length
   if it's supported by the Windows version we are running on.

Patch by: Evgeny Kotkov 

Index: include/arch/win32/apr_arch_misc.h
===
--- include/arch/win32/apr_arch_misc.h  (revision 1801660)
+++ include/arch/win32/apr_arch_misc.h  (working copy)
@@ -105,7 +105,8 @@ typedef enum {
 APR_WIN_XP_SP2 =   62,
 APR_WIN_2003 = 70,
 APR_WIN_VISTA =80,
-APR_WIN_7  =   90
+APR_WIN_7  =   90,
+APR_WIN_8  =   100
 } apr_oslevel_e;
 
 extern APR_DECLARE_DATA apr_oslevel_e apr_os_level;
Index: misc/win32/misc.c
===
--- misc/win32/misc.c   (revision 1801660)
+++ misc/win32/misc.c   (working copy)
@@ -99,8 +99,10 @@ apr_status_t apr_get_oslevel(apr_oslevel_e *level)
 else if (oslev.dwMajorVersion == 6) {
 if (oslev.dwMinorVersion == 0)
 apr_os_level = APR_WIN_VISTA;
+else if (oslev.dwMinorVersion == 1)
+apr_os_level = APR_WIN_7;
 else
-apr_os_level = APR_WIN_7;
+apr_os_level = APR_WIN_8;
 }
 else {
 apr_os_level = APR_WIN_XP;
Index: network_io/win32/sockets.c
===
--- network_io/win32/sockets.c  (revision 1801660)
+++ network_io/win32/sockets.c  (working copy)
@@ -24,6 +24,13 @@
 #include "apr_arch_inherit.h"
 #include "apr_arch_misc.h"
 
+/* Borrow the definition of SOMAXCONN_HINT() from Windows SDK 8,
+ * in case the SDK we are building against doesn't have it.
+ */
+#ifndef SOMAXCONN_HINT
+#define SOMAXCONN_HINT(b) (-(b))
+#endif
+
 static char generic_inaddr_any[16] = {0}; /* big enough for IPv4 or IPv6 */
 
 static apr_status_t socket_cleanup(void *sock)
@@ -223,7 +230,21 @@ APR_DECLARE(apr_status_t) apr_socket_bind(apr_sock
 APR_DECLARE(apr_status_t) apr_socket_listen(apr_socket_t *sock,
 apr_int32_t backlog)
 {
-if (listen(sock->socketdes, backlog) == SOCKET_ERROR)
+int backlog_val;
+
+if (apr_os_level >= APR_WIN_8) {
+/* Starting from Windows 8, listen() accepts a special SOMAXCONN_HINT()
+ * arg that allows setting the listen backlog value to a larger
+ * value than the predefined Winsock 2 limit (several hundred).
+ * 
https://blogs.msdn.microsoft.com/winsdk/2015/06/01/winsocks-listen-backlog-offers-more-flexibility-in-windows-8/
+ */
+backlog_val = SOMAXCONN_HINT(backlog);
+}
+else {
+backlog_val = backlog;
+}
+
+if (listen(sock->socketdes, backlog_val) == SOCKET_ERROR)
 return apr_get_netos_error();
 else
 return APR_SUCCESS;


[PATCH] Allow larger apr_socket_listen() backlog queue lengths on Windows 8+

2017-07-11 Thread Evgeny Kotkov
Hi everyone,

Starting with Windows 8, the socket listen() function accepts a special
SOMAXCONN_HINT(N) argument that allows making the backlog queue
length larger than the otherwise predefined limit of around 200:

  https://msdn.microsoft.com/en-us/library/windows/desktop/ms739168
  
https://blogs.msdn.microsoft.com/winsdk/2015/06/01/winsocks-listen-backlog-offers-more-flexibility-in-windows-8/

Having a larger listen backlog can be used for certain high performance
applications that need to handle lots of incoming connections.  One
example would be the httpd server with it's "ListenBacklog" directive
where setting it to a larger value currently allows serving more concurrent
connections on Windows with mpm_winnt.

The attached patch starts using SOMAXCONN_HINT() in the Win32
implementation of the apr_socket_listen() function if this feature is
supported by the underlying version of Windows.

The log message is included in the beginning of the patch file.


Regards,
Evgeny Kotkov
apr_socket_listen(): Allow larger backlog queue lengths on Windows 8+.

Starting with Windows 8, the socket listen() function accepts a special
SOMAXCONN_HINT(N) argument that allows making the backlog queue
length larger than the otherwise predefined limit of around 200:

  https://msdn.microsoft.com/en-us/library/windows/desktop/ms739168
  
https://blogs.msdn.microsoft.com/winsdk/2015/06/01/winsocks-listen-backlog-offers-more-flexibility-in-windows-8/

Having a larger listen backlog can be used for certain high performance
applications that need to handle lots of incoming connections.  One
example would be the httpd server with it's "ListenBacklog" directive
where setting it to a larger value currently allows serving more concurrent
connections on Windows with mpm_winnt.

* include/arch/win32/apr_arch_misc.h
  (enum apr_oslevel_e): Add APR_WIN_8.

* misc/win32/misc.c
  (apr_get_oslevel): Determine whether we are running on Windows 7 or
   on Windows 8+.

* network_io/win32/sockets.c
  (apr_socket_listen): Use SOMAXCONN_HINT() if it's supported by both
   the SDK we are building against and the Windows version we are
   running on.

Patch by: Evgeny Kotkov 

Index: include/arch/win32/apr_arch_misc.h
===
--- include/arch/win32/apr_arch_misc.h  (revision 1801587)
+++ include/arch/win32/apr_arch_misc.h  (working copy)
@@ -105,7 +105,8 @@ typedef enum {
 APR_WIN_XP_SP2 =   62,
 APR_WIN_2003 = 70,
 APR_WIN_VISTA =80,
-APR_WIN_7  =   90
+APR_WIN_7  =   90,
+APR_WIN_8  =   100
 } apr_oslevel_e;
 
 extern APR_DECLARE_DATA apr_oslevel_e apr_os_level;
Index: misc/win32/misc.c
===
--- misc/win32/misc.c   (revision 1801587)
+++ misc/win32/misc.c   (working copy)
@@ -99,8 +99,10 @@ apr_status_t apr_get_oslevel(apr_oslevel_e *level)
 else if (oslev.dwMajorVersion == 6) {
 if (oslev.dwMinorVersion == 0)
 apr_os_level = APR_WIN_VISTA;
+else if (oslev.dwMinorVersion == 1)
+apr_os_level = APR_WIN_7;
 else
-apr_os_level = APR_WIN_7;
+apr_os_level = APR_WIN_8;
 }
 else {
 apr_os_level = APR_WIN_XP;
Index: network_io/win32/sockets.c
===
--- network_io/win32/sockets.c  (revision 1801587)
+++ network_io/win32/sockets.c  (working copy)
@@ -223,7 +223,20 @@ APR_DECLARE(apr_status_t) apr_socket_bind(apr_sock
 APR_DECLARE(apr_status_t) apr_socket_listen(apr_socket_t *sock,
 apr_int32_t backlog)
 {
-if (listen(sock->socketdes, backlog) == SOCKET_ERROR)
+int backlog_val = backlog;
+
+#ifdef SOMAXCONN_HINT
+if (apr_os_level >= APR_WIN_8) {
+/* Starting from Windows 8, listen() accepts a special SOMAXCONN_HINT()
+ * arg that allows setting the listen backlog value to a larger
+ * value than the predefined Winsock 2 limit (several hundred).
+ * 
https://blogs.msdn.microsoft.com/winsdk/2015/06/01/winsocks-listen-backlog-offers-more-flexibility-in-windows-8/
+ */
+backlog_val = SOMAXCONN_HINT(backlog);
+}
+#endif
+
+if (listen(sock->socketdes, backlog_val) == SOCKET_ERROR)
 return apr_get_netos_error();
 else
 return APR_SUCCESS;


[PATCH] Fix apr_file_trunc() for buffered files on Windows and Unix

2017-02-28 Thread Evgeny Kotkov
Hi everyone,

This patch fixes two issues with apr_file_trunc() for buffered files:

 - The Win32 implementation incorrectly flushes the buffered writes _after_
   truncating a file.  Such files will have unexpected data written after
   the position at which they should've been truncated.  (Under Unix,
   this issue has been fixed in https://svn.apache.org/r100)

 - Both Win32 and Unix implementations incorrectly keep the data read into
   a buffer after the file is truncated.  Thus, reading from a file after
   apr_file_trunc() can return invalid data from the previous file offset.

Log message:
[[[
Fix two issues with apr_file_trunc() for buffered files:

 - The Win32 implementation incorrectly flushes the buffered writes _after_
   truncating a file.  Such files will have unexpected data written after
   the position at which they should've been truncated.  (Under Unix,
   this issue has been fixed in https://svn.apache.org/r100)

 - Both Win32 and Unix implementations incorrectly keep the data read into
   a buffer after the file is truncated.  Thus, reading from a file after
   apr_file_trunc() can return invalid data from the previous file offset.

* file_io/win32/seek.c
  (apr_file_trunc): Flush the write buffer or discard the read buffer
   before truncating.  Propely update the internal file offset (filePtr)
   and the eof_hit marker.

* file_io/unix/seek.c
  (apr_file_trunc): Discard the read buffer before truncating.

* test/testfile.c
  (test_file_trunc): Extend the checks.  Factor out part of this test...
  (test_file_trunc_buffered_write): ...into this new test.
  (test_file_trunc_buffered_write2, test_file_trunc_buffered_read): New tests.
  (testfile): Run the new tests.

Patch by: Evgeny Kotkov 
]]]


Regards,
Evgeny Kotkov
Index: file_io/unix/seek.c
===
--- file_io/unix/seek.c (revision 1784526)
+++ file_io/unix/seek.c (working copy)
@@ -117,6 +117,13 @@ apr_status_t apr_file_trunc(apr_file_t *fp, apr_of
 /* Reset buffer positions for write mode */
 fp->bufpos = fp->direction = fp->dataRead = 0;
 }
+else if (fp->direction == 0) {
+/* Discard the read buffer, as we are about to reposition
+ * ourselves to the end of file.
+ */
+fp->bufpos = 0;
+fp->dataRead = 0;
+}
 file_unlock(fp);
 if (rc) {
 return rc;
Index: file_io/win32/seek.c
===
--- file_io/win32/seek.c(revision 1784526)
+++ file_io/win32/seek.c(working copy)
@@ -161,17 +161,43 @@ APR_DECLARE(apr_status_t) apr_file_trunc(apr_file_
 LONG  offhi = (LONG)(offset >> 32);
 DWORD rc;
 
+if (thefile->buffered) {
+if (thefile->direction == 1) {
+/* Figure out what needs to be flushed.  Don't flush the part
+ * of the write buffer that will get truncated anyway.
+ */
+if (offset < thefile->filePtr) {
+thefile->bufpos = 0;
+}
+else if (offset < thefile->filePtr + (apr_off_t)thefile->bufpos) {
+thefile->bufpos = offset - thefile->filePtr;
+}
+
+if (thefile->bufpos != 0) {
+rv = apr_file_flush(thefile);
+if (rv != APR_SUCCESS)
+return rv;
+}
+}
+else if (thefile->direction == 0) {
+/* Discard the read buffer, as we are about to reposition
+ * ourselves to the end of file.
+ */
+thefile->bufpos = 0;
+thefile->dataRead = 0;
+}
+}
+
 rc = SetFilePointer(thefile->filehand, offlo, , FILE_BEGIN);
 if (rc == 0x)
 if ((rv = apr_get_os_error()) != APR_SUCCESS)
 return rv;
+thefile->filePtr = offset;
+/* Don't report EOF until the next read. */
+thefile->eof_hit = 0;
 
 if (!SetEndOfFile(thefile->filehand))
 return apr_get_os_error();
 
-if (thefile->buffered) {
-return setptr(thefile, offset);
-}
-
 return APR_SUCCESS;
 }
Index: test/testfile.c
===
--- test/testfile.c (revision 1784526)
+++ test/testfile.c (working copy)
@@ -822,10 +822,10 @@ static void test_file_trunc(abts_case *tc, void *d
 const char *s;
 apr_size_t nbytes;
 apr_finfo_t finfo;
+char c;
 
 apr_file_remove(fname, p);
 
-/* Test unbuffered */
 rv = apr_file_open(, fname,
 APR_FOPEN_CREATE | APR_FOPEN_READ |
 APR_FOPEN_WRITE,
@@ -839,15 +839,40 @@ static void test_file_trunc(abts_case *tc, void *d
 ABTS_SIZE_EQUAL(tc, strlen(s), nbytes);
 rv = apr_file_trunc(f, 4);
 ABTS_INT_EQUAL(tc, A

[PATCH] Optimize apr_file_gets() for buffered files on Windows

2017-02-04 Thread Evgeny Kotkov
Hi everyone,

The Unix implementation of apr_file_gets() features an optimization
for buffered files that avoids calling apr_file_read() / memcpy() for
one byte, and directly uses the buffered data.  This optimization was
added in https://svn.apache.org/r65294

The attached patch implements the same optimization in the Win32 version
of apr_file_gets(), and extends the corresponding part of the test suite.

This makes the function roughly 4 times faster than before.  It is heavily
used by the Subversion's filesystem layer and various modules in the
HTTP Server, e.g., mod_negotiation, and these or other existing callers
would benefit from this optimization.

Log message:
[[[
Win32: Improve apr_file_gets() performance on buffered files by not calling
apr_file_read() on each byte.

The benchmark shows that this makes the function roughly 4 times faster:

  4.202 ms -> 1.042 ms   (I measured multiple calls for the same test file)

Also see https://svn.apache.org/r65294

* file_io/win32/readwrite.c
  (apr_file_read): Factor out the part of this function that handles
   reading from buffered files ...
  (read_buffered): ...into this new helper.
  (apr_file_gets): Use the buffer directly for buffered files, the same
   way as in the Unix implementation.

* test/testfile.c
  (test_gets, test_gets_buffered): Extend these tests with read-after-EOF
   checks.
  (test_gets_empty, test_gets_multiline, test_gets_small_buf,
   test_gets_ungetc, test_gets_buffered_big): New tests.
  (testfile): Run the new tests.

Patch by: Evgeny Kotkov 
]]]


Regards,
Evgeny Kotkov
Index: file_io/win32/readwrite.c
===
--- file_io/win32/readwrite.c   (revision 1781470)
+++ file_io/win32/readwrite.c   (working copy)
@@ -140,6 +140,56 @@ static apr_status_t read_with_timeout(apr_file_t *
 return rv;
 }
 
+static apr_status_t read_buffered(apr_file_t *thefile, void *buf, apr_size_t 
*len)
+{
+apr_status_t rv;
+char *pos = (char *)buf;
+apr_size_t blocksize;
+apr_size_t size = *len;
+
+if (thefile->direction == 1) {
+rv = apr_file_flush(thefile);
+if (rv != APR_SUCCESS) {
+return rv;
+}
+thefile->bufpos = 0;
+thefile->direction = 0;
+thefile->dataRead = 0;
+}
+
+rv = 0;
+while (rv == 0 && size > 0) {
+if (thefile->bufpos >= thefile->dataRead) {
+apr_size_t read;
+rv = read_with_timeout(thefile, thefile->buffer,
+   thefile->bufsize, );
+if (read == 0) {
+if (rv == APR_EOF)
+thefile->eof_hit = TRUE;
+break;
+}
+else {
+thefile->dataRead = read;
+thefile->filePtr += thefile->dataRead;
+thefile->bufpos = 0;
+}
+}
+
+blocksize = size > thefile->dataRead - thefile->bufpos ? 
thefile->dataRead - thefile->bufpos : size;
+memcpy(pos, thefile->buffer + thefile->bufpos, blocksize);
+thefile->bufpos += blocksize;
+pos += blocksize;
+size -= blocksize;
+}
+
+*len = pos - (char *)buf;
+if (*len) {
+rv = APR_SUCCESS;
+}
+
+return rv;
+}
+
 APR_DECLARE(apr_status_t) apr_file_read(apr_file_t *thefile, void *buf, 
apr_size_t *len)
 {
 apr_status_t rv;
@@ -177,57 +227,10 @@ APR_DECLARE(apr_status_t) apr_file_read(apr_file_t
 }
 }
 if (thefile->buffered) {
-char *pos = (char *)buf;
-apr_size_t blocksize;
-apr_size_t size = *len;
-
 if (thefile->flags & APR_FOPEN_XTHREAD) {
 apr_thread_mutex_lock(thefile->mutex);
 }
-
-if (thefile->direction == 1) {
-rv = apr_file_flush(thefile);
-if (rv != APR_SUCCESS) {
-if (thefile->flags & APR_FOPEN_XTHREAD) {
-apr_thread_mutex_unlock(thefile->mutex);
-}
-return rv;
-}
-thefile->bufpos = 0;
-thefile->direction = 0;
-thefile->dataRead = 0;
-}
-
-rv = 0;
-while (rv == 0 && size > 0) {
-if (thefile->bufpos >= thefile->dataRead) {
-apr_size_t read;
-rv = read_with_timeout(thefile, thefile->buffer, 
-   thefile->bufsize, );
-if (read == 0) {
-if (rv == APR_EOF)
-thefile->eof_hit = TRUE;
-break;
-}
-else {
-thefile->dataRead = read;
-thefile->filePtr += thefile->dataRead;
-thefile->bufpos = 0;
-}
-}
-
-bloc