in src/source/Signaling/LwsApiCalls.c [530:695]
STATUS lwsCompleteSync(PLwsCallInfo pCallInfo)
{
ENTERS();
STATUS retStatus = STATUS_SUCCESS;
volatile INT32 retVal = 0;
PCHAR pHostStart, pHostEnd, pVerb;
struct lws_client_connect_info connectInfo;
struct lws* clientLws;
struct lws_context* pContext;
BOOL secureConnection, locked = FALSE, serializerLocked = FALSE, iterate = TRUE;
CHAR path[MAX_URI_CHAR_LEN + 1];
CHK(pCallInfo != NULL && pCallInfo->callInfo.pRequestInfo != NULL && pCallInfo->pSignalingClient != NULL, STATUS_NULL_ARG);
CHK_STATUS(requestRequiresSecureConnection(pCallInfo->callInfo.pRequestInfo->url, &secureConnection));
DLOGV("Perform %s synchronous call for URL: %s", secureConnection ? "secure" : EMPTY_STRING, pCallInfo->callInfo.pRequestInfo->url);
if (pCallInfo->protocolIndex == PROTOCOL_INDEX_WSS) {
pVerb = NULL;
// Remove the header as it will be added back by LWS
CHK_STATUS(removeRequestHeader(pCallInfo->callInfo.pRequestInfo, (PCHAR) "user-agent"));
// Sign the request
CHK_STATUS(signAwsRequestInfoQueryParam(pCallInfo->callInfo.pRequestInfo));
// Remove the headers
CHK_STATUS(removeRequestHeaders(pCallInfo->callInfo.pRequestInfo));
} else {
pVerb = HTTP_REQUEST_VERB_POST_STRING;
// Sign the request
CHK_STATUS(signAwsRequestInfo(pCallInfo->callInfo.pRequestInfo));
// Remove the header as it will be added back by LWS
CHK_STATUS(removeRequestHeader(pCallInfo->callInfo.pRequestInfo, AWS_SIG_V4_HEADER_HOST));
}
pContext = pCallInfo->pSignalingClient->pLwsContext;
// Execute the LWS REST call
MEMSET(&connectInfo, 0x00, SIZEOF(struct lws_client_connect_info));
connectInfo.context = pContext;
connectInfo.ssl_connection = LCCSCF_USE_SSL;
connectInfo.port = SIGNALING_DEFAULT_SSL_PORT;
CHK_STATUS(getRequestHost(pCallInfo->callInfo.pRequestInfo->url, &pHostStart, &pHostEnd));
CHK(pHostEnd == NULL || *pHostEnd == '/' || *pHostEnd == '?', STATUS_INTERNAL_ERROR);
// Store the path
path[MAX_URI_CHAR_LEN] = '\0';
if (pHostEnd != NULL) {
if (*pHostEnd == '/') {
STRNCPY(path, pHostEnd, MAX_URI_CHAR_LEN);
} else {
path[0] = '/';
STRNCPY(&path[1], pHostEnd, MAX_URI_CHAR_LEN - 1);
}
} else {
path[0] = '/';
path[1] = '\0';
}
// NULL terminate the host
*pHostEnd = '\0';
connectInfo.address = pHostStart;
connectInfo.path = path;
connectInfo.host = connectInfo.address;
connectInfo.method = pVerb;
connectInfo.protocol = pCallInfo->pSignalingClient->signalingProtocols[pCallInfo->protocolIndex].name;
connectInfo.pwsi = &clientLws;
connectInfo.opaque_user_data = pCallInfo;
// Attempt to iterate and acquire the locks
// NOTE: The https protocol should be called sequentially only
MUTEX_LOCK(pCallInfo->pSignalingClient->lwsSerializerLock);
serializerLocked = TRUE;
// Ensure we are not running another https protocol
// The WSIs for all of the protocols are set and cleared in this function only.
// The HTTPS is serialized via the state machine lock and we should not encounter
// another https protocol in flight. The only case is when we have an http request
// and a wss is in progress. This is the case when we have a current websocket listener
// and need to perform an https call due to ICE server config refresh for example.
// If we have an ongoing wss operations, we can't call lws_client_connect_via_info API
// due to threading model of LWS. WHat we need to do is to wake up the potentially blocked
// ongoing wss handler for it to release the service lock which it holds while calling lws_service()
// API so we can grab the lock in order to perform the lws_client_connect_via_info API call.
// The need to wake up the wss handler (if any) to compete for the lock is the reason for this
// loop. In order to avoid pegging of the CPU while the contention for the lock happes,
// we are setting an atomic and releasing it to trigger a timed wait when the lws_service call
// awakes to make sure we are not going to starve this thread.
// NOTE: The THREAD_SLEEP calls in this routine are not designed to adjust
// the execution timing/race conditions but to eliminate a busy wait in a spin-lock
// type scenario for resource contention.
// We should have HTTPS protocol serialized at the state machine level
CHK_ERR(pCallInfo->pSignalingClient->currentWsi[PROTOCOL_INDEX_HTTPS] == NULL, STATUS_INVALID_OPERATION,
"HTTPS requests should be processed sequentially.");
// Indicate that we are trying to acquire the lock
ATOMIC_STORE_BOOL(&pCallInfo->pSignalingClient->serviceLockContention, TRUE);
while (iterate && pCallInfo->pSignalingClient->currentWsi[PROTOCOL_INDEX_WSS] != NULL) {
if (!MUTEX_TRYLOCK(pCallInfo->pSignalingClient->lwsServiceLock)) {
// Wake up the event loop
CHK_STATUS(wakeLwsServiceEventLoop(pCallInfo->pSignalingClient, PROTOCOL_INDEX_WSS));
} else {
locked = TRUE;
iterate = FALSE;
}
}
ATOMIC_STORE_BOOL(&pCallInfo->pSignalingClient->serviceLockContention, FALSE);
// Now we should be running with a lock
CHK(NULL != (pCallInfo->pSignalingClient->currentWsi[pCallInfo->protocolIndex] = lws_client_connect_via_info(&connectInfo)),
STATUS_SIGNALING_LWS_CLIENT_CONNECT_FAILED);
if (locked) {
MUTEX_UNLOCK(pCallInfo->pSignalingClient->lwsServiceLock);
locked = FALSE;
}
MUTEX_UNLOCK(pCallInfo->pSignalingClient->lwsSerializerLock);
serializerLocked = FALSE;
while (retVal >= 0 && !gInterruptedFlagBySignalHandler && pCallInfo->callInfo.pRequestInfo != NULL &&
!ATOMIC_LOAD_BOOL(&pCallInfo->callInfo.pRequestInfo->terminating)) {
if (!MUTEX_TRYLOCK(pCallInfo->pSignalingClient->lwsServiceLock)) {
THREAD_SLEEP(LWS_SERVICE_LOOP_ITERATION_WAIT);
} else {
retVal = lws_service(pContext, 0);
MUTEX_UNLOCK(pCallInfo->pSignalingClient->lwsServiceLock);
// Add a minor timeout to relinquish the thread quota to eliminate thread starvation
// when competing for the service lock
if (ATOMIC_LOAD_BOOL(&pCallInfo->pSignalingClient->serviceLockContention)) {
THREAD_SLEEP(LWS_SERVICE_LOOP_ITERATION_WAIT);
}
}
}
// Clear the wsi on exit
MUTEX_LOCK(pCallInfo->pSignalingClient->lwsSerializerLock);
pCallInfo->pSignalingClient->currentWsi[pCallInfo->protocolIndex] = NULL;
MUTEX_UNLOCK(pCallInfo->pSignalingClient->lwsSerializerLock);
CleanUp:
// Reset the lock contention indicator in case of failure
if (STATUS_FAILED(retStatus) && pCallInfo != NULL && pCallInfo->pSignalingClient != NULL) {
ATOMIC_STORE_BOOL(&pCallInfo->pSignalingClient->serviceLockContention, FALSE);
}
if (serializerLocked) {
MUTEX_UNLOCK(pCallInfo->pSignalingClient->lwsSerializerLock);
}
if (locked) {
MUTEX_UNLOCK(pCallInfo->pSignalingClient->lwsServiceLock);
}
LEAVES();
return retStatus;
}