Dryad/xcompute_native/process.cpp

711 lines
16 KiB
C++

/*
Copyright (c) Microsoft Corporation
All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License
at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions and
limitations under the License.
*/
/*++
Module Name:
process.cpp
Abstract:
This module contains the public interface and support routines for
the xcompute process functionality on top of the HPC scheduler.
Note that "process" in the xcompute terminology maps to "task" in
the HPC terminology.
--*/
#include "stdafx.h"
#include <map>
static CComAutoCriticalSection g_idLock;
static LONG g_dwNextProcessId = 1;
DWORD GetNextProcessId()
{
return InterlockedIncrement(&g_dwNextProcessId);
}
static CComAutoCriticalSection g_referenceLock;
typedef std::map<DWORD,DWORD> HandleMapT;
HandleMapT g_handleRefs;
void AddProcessHandleReference(DWORD dwId)
{
CComCritSecLock<CComAutoCriticalSection> lock(g_referenceLock);
HandleMapT::const_iterator iter = g_handleRefs.find(dwId);
if (iter == g_handleRefs.end())
{
g_handleRefs[dwId] = 1;
}
else
{
g_handleRefs[dwId] = ++(g_handleRefs[dwId]);
}
}
bool ReleaseProcessHandleReference(DWORD dwId)
{
bool bCanFree = true;
CComCritSecLock<CComAutoCriticalSection> lock(g_referenceLock);
HandleMapT::iterator iter = g_handleRefs.find(dwId);
if (iter != g_handleRefs.end())
{
DWORD dwCount = iter->second;
if (--dwCount == 0)
{
g_handleRefs.erase(iter);
}
else
{
g_handleRefs[dwId] = dwCount;
bCanFree = false;
}
}
return bCanFree;
}
XCPROCESSSTATE TranslateProcessState(
IN ProcessState fromState
)
{
XCPROCESSSTATE toState = XCPROCESSSTATE_UNSCHEDULED;
if (fromState == ProcessState::Unscheduled)
{
toState = XCPROCESSSTATE_UNSCHEDULED;
}
else if (fromState == ProcessState::SchedulingFailed)
{
toState = XCPROCESSSTATE_SCHEDULINGFAILED;
}
else if (fromState == ProcessState::AssignedToNode)
{
toState = XCPROCESSSTATE_ASSIGNEDTONODE;
}
else if (fromState == ProcessState::Running)
{
toState = XCPROCESSSTATE_RUNNING;
}
else if (fromState == ProcessState::Completed)
{
toState = XCPROCESSSTATE_COMPLETED;
}
return toState;
}
/*++
XcCreateNewProcessHandle API
Description:
Creates a new process handle for a new process in the
given Job.
This call is synchronous and does not cross
machine boundaries/process boundaries.
Note:
1.
This method just creates the handle to
the XCompute process. It does not schedule the process itself.
Use the XcScheduleProcessAPI to schedule the XCompute process.
2.
Use the XcCloseProcessHandle() to free the handle
3.
Do not copy handle using the simple assignment operator.Use the
DuplicateProcessHandle() API. Each handle variable needs to be
freed using the XcCloseProcessHandle().
Arguments:
hSession
Handle to a session associated with this call
pJobId
The Id of the job under which the process will
be created. A NULL value will cause the current
processes JobId to be automatically picked up.
NOTE:
This parameter is only interesting to the Task Scheduler.
For all other cases, it should be assined to NULL
phProcessHandle
The handle to the process.
Return Value:
XCERROR_OK
The call succeded
--*/
XCOMPUTEAPI_EXT
XCERROR
XCOMPUTEAPI
XcCreateNewProcessHandle(
IN XCSESSIONHANDLE hSession,
IN PCXC_JOBID pJobId,
OUT PXCPROCESSHANDLE phProcessHandle
)
{
DWORD dwId = GetNextProcessId();
VertexScheduler::GetInstance()->CreateVertexProcess(dwId);
*phProcessHandle = (XCPROCESSHANDLE)dwId;
AddProcessHandleReference(*((LPDWORD)phProcessHandle));
return S_OK;
}
/*++
XcOpenCurrentProcessHandle API
Description:
Opens the current processes handle
This call is synchronous and does not cross
machine boundaries/process boundaries.
Note:
1.
This method creates the handle to the current process and assigns
it to the session on that process.
2.
Use the XcCloseProcessHandle() to free the handle
3.
Do not copy handle using the simple assignment operator.Use the
DuplicateProcessHandle() API. Each handle variable needs to be
freed using the XcCloseProcessHandle().
Arguments:
hSession
Handle to a session associated with this call.
phProcessHandle
The handle to the process. This must be closed using the
XcClosePorcessHandle()
Return Value:
XCERROR_OK
The call succeded
--*/
XCOMPUTEAPI_EXT
XCERROR
XCOMPUTEAPI
XcOpenCurrentProcessHandle(
IN XCSESSIONHANDLE hSession,
OUT PXCPROCESSHANDLE phProcessHandle
)
{
*phProcessHandle = CURRENT_PROCESS_ID;
return S_OK;
}
/*++
XcCloseProcessHandle API
Description:
Closes a process handle created either by a call to
XcCreateNewProcessHandle() or XcDupProcessHandle().
This call is synchronous and does not cross
machine boundaries/process boundaries.
NOTE:
Every call to the XcCreateNewProcessHandle() or
DupProcessHandle() should ultimately
result in a call to XcCloseProcessHandle() to deallocated the handle.
Arguments:
hProcessHandle
Process handle to be closed
Return Value:
XCERROR_OK
The call succeded
--*/
XCOMPUTEAPI_EXT
XCERROR
XCOMPUTEAPI
XcCloseProcessHandle (
IN XCPROCESSHANDLE hProcessHandle
)
{
if (hProcessHandle != CURRENT_PROCESS_ID)
{
DWORD dwId = (DWORD)hProcessHandle;
if (ReleaseProcessHandleReference(dwId))
{
// No more references, free associated resources in XComputeLib.dll
VertexScheduler::GetInstance()->CloseVertexProcess(dwId);
}
}
return S_OK;
}
/*++
XcDupProcessHandle API
Description:
Duplicates a process handle. Use this api, if a copy of the
process handle is needed.
This call is synchronous and does not cross
machine boundaries/process boundaries.
NOTE:
a. Every call to the DupProcessHandle should ultimately result
in a call to XcCloseProcessHandle() to deallocated the handle.
Arguments:
hProcessHandle
Process handle to be duplicated
phDupProcessHandle
The duplicated process handle
Return Value:
XCERROR_OK
The call succeded
--*/
XCOMPUTEAPI_EXT
XCERROR
XCOMPUTEAPI
XcDupProcessHandle(
IN XCPROCESSHANDLE hProcessHandle,
OUT PXCPROCESSHANDLE phDupProcessHandle
)
{
AddProcessHandleReference((DWORD)hProcessHandle);
*phDupProcessHandle = hProcessHandle;
return S_OK;
}
/*++
XcSerializeProcessHandle API
Description:
Creates a serialized process handle. A XCompute process can serialize a
process handle, and pass it to another XCompute process where the other
XCompute process can use the XcUnSerializeProcessHandle() API, to
recreate the process handle. Then it can use that process handle to
communicate with the process. e.g by using XcSetAndGetProcessInfo() API
Arguments:
hProcessHandle
The handle to the process to serialize.
ppXcSerializedHandleBlock
The serialized process handle. Use the XcFreeMemory() API
to de-allocated the pXcSerializedHandleBlock.
pcbBlockLength
The length in bytes of the serialized process handle block
NOTE:
The UserContext assiciated with the process handle will *NOT*
be serialzed.
Return Value:
XCERROR_OK
The call succeded
--*/
XCOMPUTEAPI_EXT
XCERROR
XCOMPUTEAPI
XcSerializeProcessHandle (
IN XCPROCESSHANDLE hProcessHandle,
OUT PCVOID* ppXcSerializedHandleBlock,
OUT PSIZE_T pcbBlockLength
)
{
*ppXcSerializedHandleBlock = (PCVOID)hProcessHandle;
*pcbBlockLength = sizeof(DWORD);
return S_OK;
}
/*++
XcUnSerializeProcessHandle API
Description:
Un-serializes a serialized process handle. See XcSerializeProcessHandle() API
for more details
Arguments:
hSession
The session to which to associate the un-serialized process handle with.
pXcSerializedHandleBlock
The serialized process handle.
pcbBlockLength
The length in bytes of the serialized process handle block
phProcessHandle
The un-serialized process handle.
Return Value:
XCERROR_OK
The call succeded
--*/
XCOMPUTEAPI_EXT
XCERROR
XCOMPUTEAPI
XcUnSerializeProcessHandle (
IN XCSESSIONHANDLE hSession,
IN PCVOID pXcSerializedHandleBlock,
IN SIZE_T cbBlockLength,
OUT PXCPROCESSHANDLE phProcessHandle
)
{
*phProcessHandle = (XCPROCESSHANDLE)pXcSerializedHandleBlock;
return S_OK;
}
/*++
XcGetProcessState API
Description:
Gets the process state information. If Schedule process is not
yet been called, the API will return error.
This call is synchronous and does not cross
machine boundaries/process boundaries.
Arguments:
hProcessHandle
Process handle
pProcessState
Describes the process state. The different states
are described in XComputeTypes.h
pProcessSchedulingError
if process state is XCPROCESSSTATE_COMPLETED
then the error code indicates reson.
S_OK means process compeleted without errors.
Other error codes indicate reasons for failed completion.
Return Value:
XCERROR_OK
The call succeded
--*/
XCOMPUTEAPI_EXT
XCERROR
XCOMPUTEAPI
XcGetProcessState(
IN XCPROCESSHANDLE hProcessHandle,
OUT PXCPROCESSSTATE pProcessState,
OUT XCERROR* pProcessSchedulingError
)
{
HRESULT hr = S_OK;
if (pProcessState == NULL)
{
hr = E_INVALIDARG;
goto Exit;
}
if (pProcessSchedulingError == NULL)
{
hr = E_INVALIDARG;
goto Exit;
}
try
{
ProcessState state = VertexScheduler::GetInstance()->GetProcessState((int)hProcessHandle);
*pProcessState = TranslateProcessState(state);
switch (*pProcessState)
{
case XCPROCESSSTATE_SCHEDULINGFAILED:
*pProcessSchedulingError = E_FAIL;
break;
case XCPROCESSSTATE_COMPLETED:
if (VertexScheduler::GetInstance()->ProcessCancelled((int)hProcessHandle))
{
*pProcessSchedulingError = E_FAIL;
}
break;
default:
*pProcessSchedulingError = S_OK;
break;
}
}
catch (System::Exception ^e)
{
hr = System::Runtime::InteropServices::Marshal::GetHRForException(e);
}
Exit:
return hr;
}
/*++
XcGetProcessId API
Description:
Gets the process Id of the process associated with the process handle.
If the process state is anything less than XCPROCESSSTATE_ASSIGNEDTOPN
an error is returned.
Arguments:
hProcessHandle
Process handle
pProcessId
The id of the process
Return Value:
XCERROR_OK
The call succeded
--*/
XCOMPUTEAPI_EXT
XCERROR
XCOMPUTEAPI
XcGetProcessId(
IN XCPROCESSHANDLE hProcessHandle,
OUT XC_PROCESSID* pProcessId
)
{
// TODO: anything else needed here?
pProcessId->Data1 = (DWORD)hProcessHandle;
return S_OK;
}
/*++
XcWaitForStateChange API
Description:
The API allows users to get async completion status for
XCompute process when it reaches a desired state. (see XCPROCESSSTATE)
When the desired state is reached the async completion is dispatched.
NOTE:
1. If the process gets cancelled, then completion is dispatched immediately
2. The pOperationState of the AsyncInfo will have the error code.
Arguments:
hProcessHandle
Handle to an XCompute process for which the
state change event is needed
waitForState
The state to wait for the XCompute to be in, so
that completion can be dispatched
tiMaxWaitInterval
The maximum amount of time (not including network
request latencies) that the API should wait for a
change in the process list before completing. If
XCTIMEINTERVAL_ZERO, the API will return changes
that can be immediately determined without
communication with the process scheduler. If
XCTIMEINTERVAL_INFINITE, the API will wait until a
change occurs or the process is cancelled.
pAsyncInfo
The async info structure. Its an alias to the
CS_ASYNC_INFO defined in Cosmos.h. If this
parameter is NULL, then the function completes in
synchronous manner and error code is returned as
return value.
If parameter is not NULL then the operation is carried
on in asynchronous manner. If an asynchronous
operation has been successfully started then
this function terminates immediately with
an HRESULT_FROM_WIN32(ERROR_IO_PENDING) return value.
Any other return value indicates that it was
impossible to start the asynchronous operation.
Return Value:
CsError_OK indicates call succeeded
--*/
XCOMPUTEAPI_EXT
XCERROR
XCOMPUTEAPI
XcWaitForStateChange(
IN XCPROCESSHANDLE hProcessHandle,
IN XCPROCESSSTATE waitForState,
IN XCTIMEINTERVAL tiMaxWaitInterval,
IN PCXC_ASYNC_INFO pAsyncInfo
)
{
ProcessState targetState;
PASYNC async;
//
// Map the requested state into the HPC task state
//
switch (waitForState)
{
case XCPROCESSSTATE_UNSCHEDULED:
targetState = ProcessState::Unscheduled;
break;
case XCPROCESSSTATE_SCHEDULINGFAILED:
targetState = ProcessState::SchedulingFailed;
break;
case XCPROCESSSTATE_ASSIGNEDTONODE:
targetState = ProcessState::AssignedToNode;
break;
case XCPROCESSSTATE_RUNNING:
targetState = ProcessState::Running;
break;
case XCPROCESSSTATE_COMPLETED:
targetState = ProcessState::Completed;
break;
default:
return E_NOTIMPL;
}
CAPTURE_ASYNC(async);
try
{
VertexScheduler ^client = VertexScheduler::GetInstance();
ProcessState currentState = client->GetProcessState((int)hProcessHandle);
if (currentState >= targetState)
{
return COMPLETE_ASYNC(async, S_OK);
}
//
// If our timeout has elapsed, return timeout
//
if (tiMaxWaitInterval == XCTIMEINTERVAL_ZERO)
{
return COMPLETE_ASYNC(async, HRESULT_FROM_WIN32(ERROR_TIMEOUT));
}
if (async == NULL)
{
if (client->WaitForStateChange((int)hProcessHandle, tiMaxWaitInterval, targetState))
{
return S_OK;
}
else
{
return HRESULT_FROM_WIN32(ERROR_TIMEOUT);
}
}
else
{
AsyncWrapper ^wrapper = gcnew AsyncWrapper(async);
StateChangeEventHandler ^handler = gcnew StateChangeEventHandler(wrapper, &AsyncWrapper::StateChangeHandler);
client->NotifyStateChange((int)hProcessHandle, tiMaxWaitInterval, targetState, handler);
return HRESULT_FROM_WIN32(ERROR_IO_PENDING);
}
}
catch (System::Exception ^e)
{
return COMPLETE_ASYNC(async,System::Runtime::InteropServices::Marshal::GetHRForException(e));
}
}