Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add preemptible work item APIs to cxplat library #94

Merged
merged 5 commits into from
Aug 28, 2023
Merged

Add preemptible work item APIs to cxplat library #94

merged 5 commits into from
Aug 28, 2023

Conversation

dthaler
Copy link
Contributor

@dthaler dthaler commented Aug 25, 2023

  • Move preemptible work item APIs from usersim library to cxplat library
  • Move preemptible rundown protection APIs from usersim library to cxplat library
  • Add tests for the above
  • Fix initialization bug in aligned allocate

Comment on lines +10 to +14
* @brief This following class implements a mock of the Windows Kernel's rundown reference implementation.
* 1) It uses a map to track the number of references to a given cxplat_rundown_reference_t structure.
* 2) The address of the cxplat_rundown_reference_t structure is used as the key to the map.
* 3) A single condition variable is used to wait for the ref count of any cxplat_rundown_reference_t structure to reach 0.
* 4) The class is a singleton and is created during static initialization and destroyed during static destruction.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whis is this so complicated, and not simply a ref count and a completion event? Is it purely for debugging reasons? If so, we need a hyper optimized release mode version.

Copy link
Contributor Author

@dthaler dthaler Aug 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was already this way in the usersim repo, all I did was rename it and move it to another file.
@Alan-Jowett was this originally your code?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The closest C++ equivalent of a Windows event is a condition variable. The C++ version was used to permit this code to be more easily made cross-platform.

An event / condition_variable per run-down reference would leak as there is no "free" API in kernel mode.

That said, it we don't care about Linux support, we could switch this to WaitOnAddress/WakeByAddress which is similar to this code, but implemented in the OS.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal first and foremost is performance. Code simplicity is secondary. So, it's perfectly fine to map the abstraction to whatever is most performant for each platform. If Linux has something different, that's ok.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should move any workarounds for "no free API in kernel mode" over to the usersim directory and keep the complexity out of the cxplat directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed issue #98

cxplat_status_t result = CXPLAT_STATUS_SUCCESS;

if (!cxplat_acquire_rundown_protection(&_cxplat_preemptible_work_items_rundown_reference)) {
return CXPLAT_STATUS_UNSUCCESSFUL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of unsuccessful, can we do something like invalid state? Because if this fails, it means we haven't initialized or are shutting down, right (i.e. not the correct/expected state)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see an NTSTATUS value with quite that meaning, so changed to use NOT_SUPPORTED instead of UNSUCCESSFUL.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While not 100% exact, we have this in msquic:

#define QUIC_STATUS_INVALID_STATE           STATUS_INVALID_DEVICE_STATE         // 0xc0000184

https://github.com/microsoft/msquic/blob/main/src/inc/msquic_winkernel.h#L97

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

STATUS_INVALID_DEVICE_STATE ("The device is not in a valid state to perform this request.") implies a device is involved, and maps to win32 ERROR_BAD_COMMAND ("The device does not recognize the command.").

STATUS_INVALID_STATE_TRANSITION ("The requested state transition is invalid and cannot be performed.") on the other hand seems to be more generic, and maps to win32 ERROR_INVALID_STATE ("The group or resource is not in the correct state to perform the requested operation.")

Since STATUS_INVALID_STATE_TRANSITION seems to be more in the spirit here, I will use that, since a "device" per se may not be involved.

return CXPLAT_STATUS_UNSUCCESSFUL;
}

*work_item = (cxplat_preemptible_work_item_t*)cxplat_allocate(sizeof(cxplat_preemptible_work_item_t));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, we should have special tags for all internally allocated objects so we can track them appropriately.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. it was already this way in the usersim repo (and the ebpf-for-windows repo before that) though, I just moved/renamed the function. Filed #97.

// initialize the _callback_environment structure before calling CreateThreadpoolWork.
(*work_item)->work = CreateThreadpoolWork(_cxplat_preemptible_routine, *work_item, &_callback_environment);
if ((*work_item)->work == nullptr) {
result = CXPLAT_STATUS_FROM_WIN32(GetLastError());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're going to need to add logging eventually at every first point of failure, like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree

Comment on lines +114 to +115
SetThreadpoolThreadMaximum(_pool, 1);
return_value = SetThreadpoolThreadMinimum(_pool, 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps some code comments to explain why this is done?

Copy link
Contributor Author

@dthaler dthaler Aug 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Alan-Jowett was this originally your code? The code was already this way in this repo, this isn't new code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The minimum was to ensure that we make forward progress on low memory. The maximum was to catch cases where work-items depend on each other.

I.e. if work item A blocks while executing waiting on work item B, then it may deadlock on low memory. Better to deadlock all the time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like another case of the code being debug-focused, which is fine for debug builds, but not retail/release builds.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure: maybe Alan can add some ifndef NDEBUG around such code?

@@ -131,7 +131,9 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="cxplat_memory_test.cpp" />
<ClCompile Include="cxplat_rundown_test.cpp" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As more and more stuff is added, it's going to make it an even bigger job to bring Posix platform support, which is a requirement for MsQuic, into the picture here. Perhaps it's time to do this so that future PRs can then add the piecemeal support for the platform.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feel free :)
Should this move to the cxplat repo before then or after then?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move it to cxplat first.

inline cxplat_status_t
_cxplat_symbol_decoder_initialize()
{
// Initialize DbgHelp.dll.
SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES);
if (!SymInitialize(GetCurrentProcess(), nullptr, TRUE)) {
int err = GetLastError();
if (err == ERROR_INVALID_PARAMETER) {
// The process already did SymInitialize(), which is fine.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean the caller is calling _cxplat_symbol_decoder_initialize more than once? If so, what if they call _cxplat_symbol_decoder_deinitialize more than once?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it means the caller calls SymInitialize directly without using cxplat.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if it also calls the uninitialize equivalent too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API is internal, not exposed to callers, only callable inside this library, from cxplat_cleanup().
A better question is: what happens if the caller calls cxplat_cleanup() multiple times.
Filed issue #99 to track that.

@dthaler dthaler merged commit 1d9a9f4 into main Aug 28, 2023
5 checks passed
@dthaler dthaler deleted the preempt branch August 28, 2023 17:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants