Skip to content

Commit

Permalink
Merge pull request #266 from microsoft/pete-dev
Browse files Browse the repository at this point in the history
Multithreading test and documentation update
  • Loading branch information
Psychlist1972 authored Feb 9, 2024
2 parents f24f7f1 + dbb9094 commit 2043c7b
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 7 deletions.
2 changes: 1 addition & 1 deletion build/staging/version/BundleInfo.wxi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Include>
<?define SetupVersionName="Developer Preview 5" ?>
<?define SetupVersionNumber="1.0.24039.2143" ?>
<?define SetupVersionNumber="1.0.24039.2325" ?>
</Include>
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Currently, in the implementation behind the scenes, the service receives each ti
| `AddEndpointProcessingPlugin(plugin)` | Add an endpoint processing plugin to this connection |
| `RemoveEndpointProcessingPlugin(id)` | Remove an endpoint processing plugin from this connection |

> Tip: In all the functions which accept a timestamp to schedule the message, **you can send a timestamp of 0 (zero) to bypass the scheduler and send the message immediately**. Otherwise, the provided timestamp is treated as an absolute time for when the message should be sent from the service. Note that the service-based scheduler (currently based on a `std::priority_queue`) gets less efficient when there are thousands of messages in it, so it's recommended that you not schedule too many messages at a time or too far out into the future.
> Tip: In all the functions which accept a timestamp to schedule the message, **you can send a timestamp of 0 (zero) to bypass the scheduler and send the message immediately** or use the `MidiClock::TimestampConstantSendImmediately` static property. Otherwise, the provided timestamp is treated as an absolute time for when the message should be sent from the service. Note that the service-based scheduler (currently based on a `std::priority_queue`) gets less efficient when there are thousands of messages in it, so it's recommended that you not schedule too many messages at a time or too far out into the future.
## Events

Expand All @@ -67,6 +67,8 @@ Currently, in the implementation behind the scenes, the service receives each ti

When processing the `MessageReceived` event, do so quickly. This event is synchronous. If you need to do long-running processing of incoming messages, add them to your own incoming queue structure and have them processed by another application thread.

> Threading: Note that the thread the `MessageReceived` callback comes in on is not the same thread which created the connection to begin with. Windows MIDI Services uses a high-priority thread in the background, one per connection. For this reason, it's best to use the event only to receive the message and store it, not to do any additional processing on the message. The TAEF test `MidiEndpointCreationThreadTests` in the `Midi2.Client.unittests` project demonstrates how this works.
> Note: Wire up event handlers and add message processing plugins prior to calling `Open()`.
## IDL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ Indicates the message flow for a function block. Note that this is, per the spec

## IDL

MidiFunctionBlockDirectionEnum IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiFunctionBlockDirectionEnum.idl)
[MidiFunctionBlockDirectionEnum IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiFunctionBlockDirectionEnum.idl)
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ Indicates the MIDI 1.0 capability restrictions for a function block. Note that W

## IDL

MidiFunctionBlockMidi10Enum IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiFunctionBlockMidi10Enum.idl)
[MidiFunctionBlockMidi10Enum IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiFunctionBlockMidi10Enum.idl)
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ In general, these values should not restrict completely what you enable a user t

## IDL

MidiFunctionBlockUIHintEnum IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiFunctionBlockUIHintEnum.idl)
[MidiFunctionBlockUIHintEnum IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiFunctionBlockUIHintEnum.idl)
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ Indicates the message flow for a group terminal block. Note that this is, per th

## IDL

MidiGroupTerminalBlockDirectionEnum IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiGroupTerminalBlockDirectionEnum.idl)
[MidiGroupTerminalBlockDirectionEnum IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiGroupTerminalBlockDirectionEnum.idl)
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ Indicates the protocol specifics for the Group Terminal Block. Group terminal bl

## IDL

MidiGroupTerminalBlockProtocolEnum IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiGroupTerminalBlockProtocolEnum.idl)
[MidiGroupTerminalBlockProtocolEnum IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiGroupTerminalBlockProtocolEnum.idl)
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@
<ClCompile Include="MidiClockTests.cpp" />
<ClCompile Include="MidiEndpointConnectionBufferTests.cpp" />
<ClCompile Include="MidiEndpointConnectionTests.cpp" />
<ClCompile Include="MidiEndpointCreationThreadTests.cpp" />
<ClCompile Include="MidiEndpointDeviceWatcherTests.cpp" />
<ClCompile Include="MidiEndpointListenerTests.cpp" />
<ClCompile Include="MidiFunctionBlockMessageBuilderTests.cpp" />
Expand All @@ -165,6 +166,7 @@
<ClInclude Include="MidiClockTests.h" />
<ClInclude Include="MidiEndpointConnectionBufferTests.h" />
<ClInclude Include="MidiEndpointConnectionTests.h" />
<ClInclude Include="MidiEndpointCreationThreadTests.h" />
<ClInclude Include="MidiEndpointDeviceWatcherTests.h" />
<ClInclude Include="MidiEndpointListenerTests.h" />
<ClInclude Include="MidiFunctionBlockMessageBuilderTests.h" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
<ClCompile Include="MidiEndpointConnectionBufferTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="MidiEndpointCreationThreadTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h">
Expand Down Expand Up @@ -89,6 +92,9 @@
<ClInclude Include="MidiEndpointConnectionBufferTests.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="MidiEndpointCreationThreadTests.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Midi2ClientTests.rc">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@

#include "stdafx.h"

#include "MidiEndpointCreationThreadTests.h"


#include <wil\resource.h>


#define NUM_MESSAGES_TO_TRANSMIT 10

_Use_decl_annotations_
void MidiEndpointCreationThreadTests::ReceiveThreadWorker(MidiSession session, winrt::hstring endpointId)
{
wil::unique_event_nothrow allMessagesReceived;
allMessagesReceived.create();

uint32_t countMessagesReceived = 0;

auto connectionThreadId = GetCurrentThreadId();
std::cout << "Receiver: connection thread: " << connectionThreadId << std::endl;

// create the connection
auto connection = session.CreateEndpointConnection(endpointId);

auto MessageReceivedHandler = [&](IMidiMessageReceivedEventSource const& sender, MidiMessageReceivedEventArgs const& args)
{
UNREFERENCED_PARAMETER(sender);
UNREFERENCED_PARAMETER(args);

auto callbackThreadId = GetCurrentThreadId();
std::cout << "Message received on callback thread: " << callbackThreadId << std::endl;

countMessagesReceived++;

// this will not be true. The thread receiving messages is a high-priority thread the application does not control.
//VERIFY_ARE_EQUAL(connectionThreadId, callbackThreadId);

if (countMessagesReceived >= NUM_MESSAGES_TO_TRANSMIT)
{
allMessagesReceived.SetEvent();
}

};

auto revoke = connection.MessageReceived(MessageReceivedHandler);
std::cout << "Receiver: Event handler created." << std::endl;

connection.Open();
std::cout << "Receiver: Connection opened." << std::endl;

m_receiverReady.SetEvent();

std::cout << "Receiver: Waiting for messages." << std::endl;

if (!allMessagesReceived.wait(10000))
{
std::cout << "Receiver: Failure waiting for receiver to connect." << std::endl;

VERIFY_FAIL();
}

std::cout << "Receiver: " << countMessagesReceived << " messages received." << std::endl;

m_receiveComplete.SetEvent();
}


_Use_decl_annotations_
void MidiEndpointCreationThreadTests::SendThreadWorker(MidiSession session, winrt::hstring endpointId)
{
auto threadId = GetCurrentThreadId();

std::cout << "Sender thread: " << threadId << std::endl;

// create the connection
auto connection = session.CreateEndpointConnection(endpointId);
connection.Open();

for (uint32_t i = 0; i < NUM_MESSAGES_TO_TRANSMIT; i++)
{
connection.SendMessageWords(MidiClock::TimestampConstantSendImmediately(), 0x21234567);

}

std::cout << "Sender: " << NUM_MESSAGES_TO_TRANSMIT << " messages sent" << std::endl;

}



void MidiEndpointCreationThreadTests::TestCreateNewSessionMultithreaded()
{
m_receiveComplete.create();
m_receiverReady.create();

auto sessionThreadId = GetCurrentThreadId();

std::cout << "Session thread: " << sessionThreadId << std::endl;

// create session on this thread

auto session = MidiSession::CreateSession(L"Multi-threaded Test");

// create loopback A on thread A
std::thread workerThreadA(&MidiEndpointCreationThreadTests::ReceiveThreadWorker, this, session, MidiEndpointDeviceInformation::DiagnosticsLoopbackAEndpointId());
workerThreadA.detach();

if (!m_receiverReady.wait(10000))
{
std::cout << "Session: Failure waiting for receiver to connect." << std::endl;

VERIFY_FAIL();
}
else
{
std::cout << "Session: Receiver ready notification received." << std::endl;
}

// create loopback B on thread B
std::thread workerThreadB(&MidiEndpointCreationThreadTests::SendThreadWorker, this, session, MidiEndpointDeviceInformation::DiagnosticsLoopbackBEndpointId());
workerThreadB.detach();

if (!m_receiveComplete.wait(20000))
{
std::cout << "Session: Failure waiting for all messages to be received." << std::endl;

VERIFY_FAIL();
}
else
{
std::cout << "Session: Message receive complete notification received." << std::endl;
}


}



Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License
// ============================================================================
// This is part of the Windows MIDI Services App API and should be used
// in your Windows application via an official binary distribution.
// Further information: https://github.com/microsoft/MIDI/
// ============================================================================


#pragma once

using namespace winrt::Windows::Devices::Midi2;


class MidiEndpointCreationThreadTests
: public WEX::TestClass<MidiEndpointCreationThreadTests>
{
public:

BEGIN_TEST_CLASS(MidiEndpointCreationThreadTests)
TEST_CLASS_PROPERTY(L"TestClassification", L"Unit")
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"Windows.Devices.Midi2.dll")
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"Midi2.BluetoothMidiAbstraction.dll")
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"Midi2.DiagnosticsAbstraction.dll")
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"Midi2.KSAbstraction.dll")
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"Midi2.MidiSrvAbstraction.dll")
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"Midi2.NetworkMidiAbstraction.dll")
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"Midi2.VirtualMidiAbstraction.dll")
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"Midi2.VirtualPatchBayAbstraction.dll")
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"Minmidi.sys")
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"usbmidi2.sys")
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"MidiSrv.exe")
END_TEST_CLASS()

//TEST_CLASS_SETUP(ClassSetup);
//TEST_CLASS_CLEANUP(ClassCleanup);

//TEST_METHOD_SETUP(TestSetup);
//TEST_METHOD_CLEANUP(TestCleanup);

//Generic Tests
TEST_METHOD(TestCreateNewSessionMultithreaded);


private:

void ReceiveThreadWorker(_In_ MidiSession session, _In_ winrt::hstring endpointId);
void SendThreadWorker(_In_ MidiSession session, _In_ winrt::hstring endpointId);


wil::unique_event_nothrow m_receiverReady;

wil::unique_event_nothrow m_receiveComplete;


};

0 comments on commit 2043c7b

Please sign in to comment.