locked
Writing to StreamSocket from different threads fails

    Question

  • Consider the following code:

    template <class T>
    static void sendData(T s)
    {
    	IOutputStream^ os = s->OutputStream;
                
    	auto writer = ref new DataWriter(os);
    
    	auto data = ref new Platform::Array<unsigned char>(4);
    
    	::memcpy(data->Data, "test", 4);
    
    	writer->WriteBytes(data);
    
    	try
    	{
    		task<unsigned int>(writer->StoreAsync()).get();
    	}
    	catch (Platform::Exception^ e)
    	{
    		assert(false);
    	}
    }

    Then, at some point in time from a button handler I do something like this:

            task<void>(create_async([]() {		
    		auto s = ref new StreamSocket();
    
    		task<void>(s->ConnectAsync(ref new HostName(ref new Platform::String(L"google.com")), ref new Platform::String(L"80"))).get();
    
    		sendData(s);
    		
    		std::thread t([s](){
    			sendData(s);
    		});
    
    		t.join();	
    	})).then([](task<void> t) {});

    The second call to 'sendData' causes the assertion failure, the exception being thrown has HRESULT 0x80000013 which corresponds to:

    //
    // MessageId: RO_E_CLOSED
    //
    // MessageText:
    //
    // The object has been closed.
    //
    #define RO_E_CLOSED                      _HRESULT_TYPEDEF_(0x80000013L)

    If we change the handler code to this:

    	task<void>(create_async([]() {		
    		auto s = ref new StreamSocket();
    
    		task<void>(s->ConnectAsync(ref new HostName(ref new Platform::String(L"google.com")), ref new Platform::String(L"80"))).get();
    				
    		std::thread t([s](){
    			sendData(s);
    		});
    
    		t.join();	
    
    		sendData(s);
    	})).then([](task<void> t) {});

    I.e. the first call to 'sendData' will be from another thread and the second from the thread where the socket was created, then the SECOND call to 'sendData' will fail with same error.

    If we change 

    		auto s = ref new StreamSocket();

    to

    		auto s = ref new DatagramSocket();

    we'll get the same behavior.

    So my question is: is this normal ? Shouldn't it be possible to write data to socket from any thread not just from the one that made the first call to 'send' ?

    Monday, March 05, 2012 5:11 PM

Answers

  • Here is the outcome of the bug investigation. This use pattern should be added to the documentation moving forward.

    This behavior is by design: The DataReader
    and DataWriter take ownership of the streams that are passed to them on
    construction. I.e. when these object get freed/closed, they'll close the
    underlying stream.
    If this behavior is not desired there are two options:

    a) Call DataWriter/Reader::DetachStream() before freeing/closing the
    DataWriter/Reader. This will make sure the DataWriter/Reader will not close the
    stream
    b) Don't use DataWriter/Reader and write data to the socket stream
    directly by calling StreamSocket::OutputStream::WriteAsync().


    David Lamb

    Monday, March 19, 2012 8:29 PM
    Moderator

All replies

  • I did some more research on this, the problem seems to be even worse, consider this code:

    static void sendData(StreamSocket^ s, DataWriter^ writer)
    {	            		
    	auto data = ref new Platform::Array<unsigned char>(4);
    
    	::memcpy(data->Data, "test", 4);
    
    	writer->WriteBytes(data);
    
    	try
    	{
    		task<unsigned int>(writer->StoreAsync()).get();
    
    		task<bool>(s->OutputStream->FlushAsync()).get();
    	}
    	catch (Platform::Exception^ e)
    	{
    		assert(false);
    	}
    }

    Now, let's call it like this:

    	task<void>(create_async([]() {		
    		auto s = ref new StreamSocket();
    
    		task<void>(s->ConnectAsync(ref new HostName(ref new Platform::String(L"google.com")), ref new Platform::String(L"80"))).get();
    
    		{
    			auto writer = ref new DataWriter(s->OutputStream);
    			sendData(s, writer);
    			sendData(s, writer);
    		}
    	})).then([](task<void> t) {});

    No problem here. Now, let's do it like this:

    	task<void>(create_async([]() {		
    		auto s = ref new StreamSocket();
    
    		task<void>(s->ConnectAsync(ref new HostName(ref new Platform::String(L"google.com")), ref new Platform::String(L"80"))).get();
    
    		{
    			auto writer = ref new DataWriter(s->OutputStream);
    			sendData(s, writer);			
    		}
    
    		{
    			auto writer = ref new DataWriter(s->OutputStream);
    			sendData(s, writer);
    		}
    	})).then([](task<void> t) {});

    And we get the assertion on second call to 'sendData'. So the problem is that if I create a DataWriter once, write some data and close it I won't be able to write to the socket EVER AGAIN. So, in order to write more than once I'll have to carry around that DataWriter object together with the socket ALL THE TIME! Seems like a bug...

    Tuesday, March 06, 2012 6:32 AM
  • Thanks so much for your feedback! I agree this appears to be a bug. I would really appreciate it if you could file a bug on this. If you happen to have a sample project, please attach it in the report. Otherwise as much of the sample code needed to demonstrate the problem. Here are the instructions to file the bug:


    Thank you for posting your feedback, we want to make sure
    we get the right info including a detailed description and important log
    files.  Can you also submit feedback using the Windows Feedback Tool from
    the Microsoft Connect Site associated with the Windows 8 pre-release
    programs?  Click the follow limited use link
    to join the Connect program and then follow these steps.


    https://connect.microsoft.com/site1147/InvitationUse.aspx?ProgramID=7221&InvitationID=CB2-YH2D-TXMW


    To provide feedback once the Windows Feedback Tool is
    installed:


    1. Open the Send Feedback icon on the Start screen.
      (Note: Make sure you don’t delete the icon – once it’s deleted, it can’t be
      recovered.)
    2. On the left side of the window, select the
      relevant area for your feedback. For example, if your feedback is about
      changing the background color, you would select “Appearance and Customization.”
    3. On the right side, choose the issue that matches
      your feedback as closely as possible, and then click or tap Next.
    4. Enter a title that describes your issue. For
      example, “Windows hangs during shutdown after installing updates.”
    5. In the next field, describe step-by-step what
      you were doing when the issue occurred. This will help us reproduce the
      problem.
    6. In the bottom field of the screen, provide as
      much additional detail as possible about the issue, and then click or tap Next.
    7. Review the information being reported about your
      system. Add any files necessary to reproduce the issue and add a screenshot if
      the problem is still visible on your screen. Then, click or tap Send Report.


    David Lamb


    Tuesday, March 06, 2012 10:21 PM
    Moderator
  • Thanks, I've created the ticket for this issue
    Wednesday, March 07, 2012 11:43 AM
  • Thanks so much. I appreciate it. If there are any updates, I'll make sure to update you here.

    David Lamb

    Wednesday, March 07, 2012 4:19 PM
    Moderator
  • Here is the outcome of the bug investigation. This use pattern should be added to the documentation moving forward.

    This behavior is by design: The DataReader
    and DataWriter take ownership of the streams that are passed to them on
    construction. I.e. when these object get freed/closed, they'll close the
    underlying stream.
    If this behavior is not desired there are two options:

    a) Call DataWriter/Reader::DetachStream() before freeing/closing the
    DataWriter/Reader. This will make sure the DataWriter/Reader will not close the
    stream
    b) Don't use DataWriter/Reader and write data to the socket stream
    directly by calling StreamSocket::OutputStream::WriteAsync().


    David Lamb

    Monday, March 19, 2012 8:29 PM
    Moderator
  • How to call StreamSocket::OutputStream::WriteAsync() directly? Without DataWriter, how to convert data to IBuffer as input parameter for WriteAsync()?

    Tuesday, March 27, 2012 5:54 AM
  • Hi David,

    You made my day. I have been trying to fix my proxy from past week. I almost rewrote it trying to fix this issue and at the end fix comes out to be a sing line of code.

    DataReader/DataWriter.DetachStream()

    Thanks a ton. Cheers! :)

    Monday, October 13, 2014 7:30 AM