A while ago, I noticed that our setup program didn't work with cvs wine. Somebody said "Oh, that's because named pipes are broken." Since then, the setup program works again with cvs wine, but I'm still curious about the state of named pipes. The simple sequence CreateNamedPipe CreateFile seems to work under Windows, but not under Wine. Does Wine for some reason require a ConnectNamedPipe between the two? A very simple test is attached. (BTW, I've been looking at the wineserver named pipe code a bit. Shame it's not better commented... at least it's small, so the lack of comments isn't too awful.) - Dan -- Dan Kegel http://www.kegel.com http://counter.li.org/cgi-bin/runscript/display-person.cgi?user=78045 Index: dlls/kernel/tests/Makefile.in =================================================================== RCS file: /home/wine/wine/dlls/kernel/tests/Makefile.in,v retrieving revision 1.6 diff -u -p -u -r1.6 Makefile.in --- dlls/kernel/tests/Makefile.in 4 Oct 2002 17:42:27 -0000 1.6 +++ dlls/kernel/tests/Makefile.in 13 Feb 2003 16:54:45 -0000 @@ -17,6 +17,7 @@ CTESTS = \ generated.c \ locale.c \ path.c \ + pipe.c \ process.c \ thread.c --- /dev/null 2002-08-30 16:31:37.000000000 -0700 +++ dlls/kernel/tests/pipe.c 2003-02-17 17:26:42.000000000 -0800 @@ -0,0 +1,80 @@ +/* + * Unit tests for named pipe functions in Wine + * + * Copyright (c) 2002 Dan Kegel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <stdlib.h> +#include <stdio.h> +#include <time.h> + +#ifndef STANDALONE +#include "wine/test.h" +#else +#include <assert.h> +#define START_TEST(name) main(int argc, char **argv) +#define ok(condition, msg) assert(condition) +#endif + +#include <windef.h> +#include <winbase.h> +#include <winerror.h> +#include <wtypes.h> + +#define PIPENAME "\\\\.\\PIPE\\tests_pipe" + +void test_CreateNamedPipeA(void) +{ + HANDLE hnp; + HANDLE hFile; + + /* Functional checks + * Just check things used internally by wine, in e.g. + * wine/dlls/ole32/compobj.c + * wine/programs/rpcss/np_server.c + * wine/dlls/rpcrt4/rpc_binding.c + */ + + hnp = CreateNamedPipeA(PIPENAME, + PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, + 4096, + 4096, + NMPWAIT_USE_DEFAULT_WAIT, + NULL); + ok(hnp != INVALID_HANDLE_VALUE, "CreateNamedPipe failed"); + + /* in wine, the CreateFile is in a different thread, but that + * shouldn't matter. Also, the first thread does a ConnectNamedPipe, + * but that isn't required by the Win32 API, it's optional. + */ + hFile = CreateFileA(PIPENAME, GENERIC_READ|GENERIC_WRITE, 0, + NULL, OPEN_EXISTING, 0, 0); + ok(hFile != INVALID_HANDLE_VALUE, "CreateFile failed"); + + if (hFile != INVALID_HANDLE_VALUE) { + CloseHandle(hFile); + } + + CloseHandle(hnp); +} + +START_TEST(pipe) +{ + test_CreateNamedPipeA(); +} +
On Monday 17 February 2003 08:26 pm, Dan Kegel wrote:
A while ago, I noticed that our setup program didn't work with cvs wine. Somebody said "Oh, that's because named pipes are broken." Since then, the setup program works again with cvs wine, but I'm still curious about the state of named pipes.
The simple sequence CreateNamedPipe CreateFile seems to work under Windows, but not under Wine. Does Wine for some reason require a ConnectNamedPipe between the two? A very simple test is attached.
(BTW, I've been looking at the wineserver named pipe code a bit. Shame it's not better commented... at least it's small, so the lack of comments isn't too awful.) - Dan
nice catch! This explains some things I've been noticing lately ;) There have been race-conditions in RPC lately, my uncomitted "rpc testcase" patch exposes them. Maybe you've found code (or hardware?) to consistently expose the losing side of the race? Wish I had a dualie for stuff like this ;) Will take a peek and see if anything jumps out at me (fat chance, I presume, but wth...) -- gmt "Of all the tyrannies, a tyranny sincerly exercised for the good of its victims may be the most opressive. It may be better to live under robber barons than under omnipotent moral busybodies. The robber baron's cruelty may sometimes sleep, his cupidity may at some point be satiated; but those who torment us for our own good will torment us without end, for they do so with the approval of their own conscience." - C. S. Lewis
Hmmm. I didn't think of that case when I wrote the code :( It seems that we need to add code to create a pipe and wait for a server (ps_wait_connect) in server/named_pipe.c, line 349, and some more to connect a new pipe server to waiting clients at line 380. If somebody wants to do it feel free, otherwise I'll turn out a patch sooner or later... Mike Gregory M. Turner wrote:
The simple sequence CreateNamedPipe CreateFile seems to work under Windows, but not under Wine.
nice catch!
This explains some things I've been noticing lately ;) There have been race-conditions in RPC lately, my uncomitted "rpc testcase" patch exposes them. Maybe you've found code (or hardware?) to consistently expose the losing side of the race? Wish I had a dualie for stuff like this ;)
Will take a peek and see if anything jumps out at me (fat chance, I presume, but wth...)
Mike McCormack wrote:
Hmmm. I didn't think of that case when I wrote the code :(
It seems that we need to add code to create a pipe and wait for a server (ps_wait_connect) in server/named_pipe.c, line 349, and some more to connect a new pipe server to waiting clients at line 380.
If somebody wants to do it feel free, otherwise I'll turn out a patch sooner or later...
Hrm. I'm trying to understand how the calling thread gets put to sleep on, say, a call to WaitNamedPipe. Can I buy a vowel? - Dan -- Dan Kegel http://www.kegel.com http://counter.li.org/cgi-bin/runscript/display-person.cgi?user=78045
Hi Dan, I don't know if this is what you want to hear, but I'm rewriting the named pipe code to handle message mode and solve the problem you pointed out, so if you wait a while, I'll have lots of new code for you... though it's probably a good idea to fix the current code first so we have a something that works before I break it again. To make a thread go to sleep, you pass in an event flag in your request, then wait on the event flag in the client after the server call returns. Mike Dan Kegel wrote:
Mike McCormack wrote:
Hmmm. I didn't think of that case when I wrote the code :(
It seems that we need to add code to create a pipe and wait for a server (ps_wait_connect) in server/named_pipe.c, line 349, and some more to connect a new pipe server to waiting clients at line 380.
If somebody wants to do it feel free, otherwise I'll turn out a patch sooner or later...
Hrm. I'm trying to understand how the calling thread gets put to sleep on, say, a call to WaitNamedPipe. Can I buy a vowel? - Dan
Mike McCormack wrote:
I don't know if this is what you want to hear, but I'm rewriting the named pipe code to handle message mode and solve the problem you pointed out, so if you wait a while, I'll have lots of new code for you... though it's probably a good idea to fix the current code first so we have a something that works before I break it again.
That is totally what I want to hear. I'm adding stuff to the named pipe regression test; any requests?
To make a thread go to sleep, you pass in an event flag in your request, then wait on the event flag in the client after the server call returns.
Ah, so the sleeping part isn't visible in server. Thanks! - Dan -- Dan Kegel http://www.kegel.com http://counter.li.org/cgi-bin/runscript/display-person.cgi?user=78045
That is totally what I want to hear. I'm adding stuff to the named pipe regression test; any requests?
Hi Dan, Well, I'm curious to know what happens when you: * put a pipe into message mode, then do partial reads. * do TransactNamedPipe, and there is already read data available in the pipe. * DisconnectNamedPipe on a pipe that still has messages in it * try reading from a pipe that is not connected (before and after connecting) * switch pipes between message mode and byte mode with data in the pipe I'm still coding it, then it needs to be debugged, reworked, cut up into patches, etc, but I'll send you a something when it doesn't crash immediately :) Mike
Mike McCormack wrote:
Well, I'm curious to know what happens when you: * put a pipe into message mode, then do partial reads. * do TransactNamedPipe, and there is already read data available in the pipe. * DisconnectNamedPipe on a pipe that still has messages in it * try reading from a pipe that is not connected (before and after connecting) * switch pipes between message mode and byte mode with data in the pipe
Er, looks like it'll be a while before I test those, but I do have a just-barely-nontrivial test; wine fails it in several ways. Attached for your testing pleasure. - Dan -- Dan Kegel http://www.kegel.com http://counter.li.org/cgi-bin/runscript/display-person.cgi?user=78045 /* * Unit tests for named pipe functions in Wine * * Copyright (c) 2002 Dan Kegel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <stdlib.h> #include <stdio.h> #include <time.h> #ifndef STANDALONE #include "wine/test.h" #else #include <assert.h> #define START_TEST(name) main(int argc, char **argv) #define ok(condition, msg) assert(condition) #define todo_wine #endif #include <wtypes.h> #include <windef.h> #include <winbase.h> #include <winerror.h> #define PIPENAME "\\\\.\\PiPe\\tests_" __FILE__ static void msg(const char *s) { DWORD cbWritten; WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), s, strlen(s), &cbWritten, NULL); } void test_CreateNamedPipeA(void) { HANDLE hnp; HANDLE hFile; const char obuf[] = "Bit Bucket"; char ibuf[32]; DWORD written; DWORD gelesen; /* Bad parameter checks */ hnp = CreateNamedPipeA("not a named pipe", PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_WAIT, /* nMaxInstances */ 1, /* nOutBufSize */ 1024, /* nInBufSize */ 1024, /* nDefaultWait */ NMPWAIT_USE_DEFAULT_WAIT, /* lpSecurityAttrib */ NULL); if (hnp == INVALID_HANDLE_VALUE && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) { /* Is this the right way to notify user of skipped tests? */ ok(hnp == INVALID_HANDLE_VALUE && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED, "CreateNamedPipe not supported on this platform, skipping tests."); return; } ok(hnp == INVALID_HANDLE_VALUE && GetLastError() == ERROR_INVALID_NAME, "CreateNamedPipe should fail if name doesn't start with \\\\.\\pipe"); hnp = CreateNamedPipeA(NULL, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_WAIT, 1, 1024, 1024, NMPWAIT_USE_DEFAULT_WAIT, NULL); ok(hnp == INVALID_HANDLE_VALUE && GetLastError() == ERROR_PATH_NOT_FOUND, "CreateNamedPipe should fail if name is NULL"); hFile = CreateFileA(PIPENAME, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0); ok(hFile == INVALID_HANDLE_VALUE && GetLastError() == ERROR_FILE_NOT_FOUND, "connecting to nonexistent named pipe should fail with ERROR_FILE_NOT_FOUND"); /* Functional checks */ hnp = CreateNamedPipeA(PIPENAME, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_WAIT, /* nMaxInstances */ 1, /* nOutBufSize */ 1024, /* nInBufSize */ 1024, /* nDefaultWait */ NMPWAIT_USE_DEFAULT_WAIT, /* lpSecurityAttrib */ NULL); ok(hnp != INVALID_HANDLE_VALUE, "CreateNamedPipe failed"); hFile = CreateFileA(PIPENAME, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0); todo_wine { ok(hFile != INVALID_HANDLE_VALUE, "CreateFile failed"); } /* don't try to do i/o if one side couldn't be opened, as it hangs */ if (hFile != INVALID_HANDLE_VALUE) { HANDLE hFile2; /* Make sure we can read and write a few bytes in both directions*/ memset(ibuf, 0, sizeof(ibuf)); ok(WriteFile(hnp, obuf, sizeof(obuf), &written, NULL), "WriteFile"); ok(written == sizeof(obuf), "write file len"); ok(ReadFile(hFile, ibuf, sizeof(obuf), &gelesen, NULL), "ReadFile"); ok(gelesen == sizeof(obuf), "read file len"); ok(memcmp(obuf, ibuf, written) == 0, "content check"); memset(ibuf, 0, sizeof(ibuf)); ok(WriteFile(hFile, obuf, sizeof(obuf), &written, NULL), "WriteFile"); ok(written == sizeof(obuf), "write file len"); ok(ReadFile(hnp, ibuf, sizeof(obuf), &gelesen, NULL), "ReadFile"); ok(gelesen == sizeof(obuf), "read file len"); ok(memcmp(obuf, ibuf, written) == 0, "content check"); /* Picky conformance tests */ /* Verify that you can't connect to pipe again * until server calls DisconnectNamedPipe+ConnectNamedPipe * or creates a new pipe * case 1: other client not yet closed */ hFile2 = CreateFileA(PIPENAME, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0); ok(hFile2 == INVALID_HANDLE_VALUE, "connecting to named pipe after other client closes but before DisconnectNamedPipe should fail"); ok(GetLastError() == ERROR_PIPE_BUSY, "connecting to named pipe before other client closes should fail with ERROR_PIPE_BUSY"); ok(CloseHandle(hFile), "CloseHandle"); /* case 2: other client already closed */ hFile = CreateFileA(PIPENAME, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0); ok(hFile == INVALID_HANDLE_VALUE, "connecting to named pipe after other client closes but before DisconnectNamedPipe should fail"); ok(GetLastError() == ERROR_PIPE_BUSY, "connecting to named pipe after other client closes but before DisconnectNamedPipe should fail with ERROR_PIPE_BUSY"); ok(DisconnectNamedPipe(hnp), "DisconnectNamedPipe"); /* case 3: server has called DisconnectNamedPipe but not ConnectNamed Pipe */ hFile = CreateFileA(PIPENAME, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0); ok(hFile == INVALID_HANDLE_VALUE, "connecting to named pipe after other client closes but before DisconnectNamedPipe should fail"); ok(GetLastError() == ERROR_PIPE_BUSY, "connecting to named pipe after other client closes but before ConnectNamedPipe should fail with ERROR_PIPE_BUSY"); /* to be complete, we'd call ConnectNamedPipe here and loop, * but by default that's blocking, so we'd either have * to turn on the uncommon nonblocking mode, or * use another thread. */ } ok(CloseHandle(hnp), "CloseHandle"); } /** implementation of alarm() */ static DWORD CALLBACK alarmThreadMain(LPVOID arg) { DWORD timeout = (DWORD) arg; msg("alarmThreadMain\n"); Sleep(timeout); ok(FALSE, "alarm"); ExitProcess(1); return 1; } /** Trivial byte echo server */ static DWORD CALLBACK serverThreadMain(LPVOID arg) { HANDLE hnp = (HANDLE) arg; int i; for (i=0; ; i++) { char buf[512]; DWORD written; DWORD gelesen; DWORD success; /* Wait for client to connect */ msg("Server calling ConnectNamedPipe...\n"); ok(ConnectNamedPipe(hnp, NULL) || GetLastError() == ERROR_PIPE_CONNECTED, "ConnectNamedPipe"); msg("ConnectNamedPipe returned.\n"); /* Echo bytes once */ memset(buf, 0, sizeof(buf)); /* Hmm. This read seems to completely hose wine. Total lockup. */ msg("Server reading...\n"); success = ReadFile(hnp, buf, sizeof(buf), &gelesen, NULL); msg("Server done reading.\n"); ok(success, "ReadFile"); msg("Server writing...\n"); ok(WriteFile(hnp, buf, gelesen, &written, NULL), "WriteFile"); msg("Server done writing.\n"); ok(written == gelesen, "write file len"); /* finish this connection, wait for next one */ ok(FlushFileBuffers(hnp), "FlushFileBuffers"); ok(DisconnectNamedPipe(hnp), "DisconnectNamedPipe"); } } void test_NamedPipe_2(void) { HANDLE hnp; HANDLE serverThread; HANDLE alarmThread; DWORD serverThreadId; DWORD alarmThreadId; int i; /* Set up a ten second timeout */ alarmThread = CreateThread(NULL,0,alarmThreadMain,(void *)10000,0,&alarmThreadId); /* Set up a simple echo server */ hnp = CreateNamedPipeA(PIPENAME, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_WAIT, /* nMaxInstances */ 1, /* nOutBufSize */ 1024, /* nInBufSize */ 1024, /* nDefaultWait */ NMPWAIT_USE_DEFAULT_WAIT, /* lpSecurityAttrib */ NULL); ok(hnp != INVALID_HANDLE_VALUE, "CreateNamedPipe failed"); serverThread = CreateThread(NULL,0,serverThreadMain,hnp,0,&serverThreadId); ok(serverThread != INVALID_HANDLE_VALUE, "CreateThread"); for (i=0; i<3; i++) { HANDLE hFile; const char obuf[] = "Bit Bucket"; char ibuf[32]; DWORD written; DWORD gelesen; int loop; for (loop=0; loop<3; loop++) { msg("Client connecting...\n"); /* Connect to the server */ hFile = CreateFileA(PIPENAME, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0); if (hFile != INVALID_HANDLE_VALUE) break; /* must be the second time around the loop, and server hasn't called ConnectNamedPipe yet */ ok(GetLastError() == ERROR_PIPE_BUSY, "connecting to pipe"); msg("connect failed, retrying\n"); Sleep(10); } ok(hFile != INVALID_HANDLE_VALUE, "client opening named pipe"); /* Make sure it can echo */ memset(ibuf, 0, sizeof(ibuf)); msg("Client writing...\n"); ok(WriteFile(hFile, obuf, sizeof(obuf), &written, NULL), "WriteFile"); ok(written == sizeof(obuf), "write file len"); msg("Client reading...\n"); ok(ReadFile(hFile, ibuf, sizeof(obuf), &gelesen, NULL), "ReadFile"); ok(gelesen == sizeof(obuf), "read file len"); ok(memcmp(obuf, ibuf, written) == 0, "content check"); msg("Client closing...\n"); ok(CloseHandle(hFile), "CloseHandle"); } ok(TerminateThread(alarmThread, 0), "TerminateThread"); ok(TerminateThread(serverThread, 0), "TerminateThread"); ok(WaitForSingleObject(serverThread,5000)==WAIT_OBJECT_0, "TerminateThread didn't work"); ok(CloseHandle(hnp), "CloseHandle"); msg("test_NamedPipe_2 done\n"); } START_TEST(pipe) { test_NamedPipe_2(); test_CreateNamedPipeA(); }
I'll keep updating http://www.kegel.com/pipe.c as I add more to the named pipe test... - Dan -- Dan Kegel http://www.kegel.com http://counter.li.org/cgi-bin/runscript/display-person.cgi?user=78045
Mike McCormack wrote:
Well, I'm curious to know what happens when you: ...
* DisconnectNamedPipe on a pipe that still has messages in it
Win2K pro: In byte oriented pipes, the bytes are lost, it appears.
* try reading from a pipe that is not connected (before and after connecting)
Win2K pro: Before connecting, you get the error ERROR_PIPE_LISTENING. This can only happen on the server side before the client opens the pipe, I think. After disconnecting, you get the error ERROR_PIPE_NOT_CONNECTED on both client and server. Wine is *not* doing well here. It crashes on a couple of these tests, and the unit test framework *doesn't notice*. See http://www.kegel.com/pipe.c for my current test. More to follow. - Dan -- Dan Kegel http://www.kegel.com http://counter.li.org/cgi-bin/runscript/display-person.cgi?user=78045
Hey Dan, Great work! Some of this problems may be solved by implementing our own pipes rather than using unix pipes. We're going to get a bit of a slow down, but I don't think there's any alternative if we want things to work correctly :( I'm starting to test my new code. After I remove all the stupid mistakes and obvious bugs, I'll send you something to have a look at. Mike Dan Kegel wrote:
Wine is *not* doing well here. It crashes on a couple of these tests, and the unit test framework *doesn't notice*. See http://www.kegel.com/pipe.c for my current test.
More to follow. - Dan
Gregory M. Turner wrote:
On Monday 17 February 2003 08:26 pm, Dan Kegel wrote:
The simple sequence CreateNamedPipe CreateFile seems to work under Windows, but not under Wine. Does Wine for some reason require a ConnectNamedPipe between the two? ...
nice catch!
This explains some things I've been noticing lately ;) There have been race-conditions in RPC lately, my uncomitted "rpc testcase" patch exposes them. Maybe you've found code (or hardware?) to consistently expose the losing side of the race? Wish I had a dualie for stuff like this ;)
No need for a dual processor to magnify this bug! Just apply the attached patch, and I bet you'll get the race to trigger every time you do an installshield install. It shows up as the dialog box "Object not registered". BTW I'm slowly adding to tests/pipe.c; I need to exercise a few more calls, then it'll be a handy little unit test for anyone fixing up the named pipe support. (Just don't forget to remove the todo_wine in the test, else it won't actually test anything.) - Dan -- Dan Kegel http://www.kegel.com http://counter.li.org/cgi-bin/runscript/display-person.cgi?user=78045 Index: dlls/ole32/rpc.c =================================================================== RCS file: /home/wine/wine/dlls/ole32/rpc.c,v retrieving revision 1.10 diff -u -p -d -u -r1.10 rpc.c --- dlls/ole32/rpc.c 14 Feb 2003 23:30:50 -0000 1.10 +++ dlls/ole32/rpc.c 22 Feb 2003 05:11:21 -0000 @@ -669,6 +669,10 @@ _StubMgrThread(LPVOID param) { FIXME("pipe creation failed for %s, le is %lx\n",pipefn,GetLastError()); return 1; /* permanent failure, so quit stubmgr thread */ } +#if 1 + /* widen the window for the race condition */ + Sleep(50); +#endif if (!ConnectNamedPipe(listenPipe,NULL)) { ERR("Failure during ConnectNamedPipe %lx!\n",GetLastError()); CloseHandle(listenPipe);
participants (3)
-
Dan Kegel -
Gregory M. Turner -
Mike McCormack