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

Make Platform functions to return az_result #1256

Merged
merged 10 commits into from
Sep 17, 2020
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Update provisioning client struct member name in `az_iot_provisioning_client_register_response` from `registration_result` to `registration_state`.
- Changed `operation_status` in `az_iot_provisioning_client_register_response` from `az_span` to `az_iot_provisioning_client_operation_status` enum.
- Removed `az_iot_provisioning_client_parse_operation_status()` from `az_iot_provisioning_client.h`.
- Platform: `az_platform_clock_msec()`, and `az_platform_sleep_msec()` return `az_result`, which is `AZ_ERROR_DEPENDENCY_NOT_PROVIDED` when `az_noplatform` implementation is used.

### Bug Fixes

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ By default, when building the project with no options, the following static libr
- az_storage_blobs
- Storage SDK blobs client.
- az_noplatform
- A platform abstraction which will compile but returns 0 or does nothing for all platform calls. This ensures the project can be compiled without the need to provide any specific platform implementation. This is useful if you want to use az_core without platform specific functions like `time` or `sleep`.
- A platform abstraction which will compile but returns `AZ_ERROR_DEPENDENCY_NOT_PROVIDED` from all its functions. This ensures the project can be compiled without the need to provide any specific platform implementation. This is useful if you want to use az_core without platform specific functions like `time` or `sleep`.
- az_nohttp
- Library that provides a no-op HTTP stack, returning `AZ_ERROR_DEPENDENCY_NOT_PROVIDED`. Similar to `az_noplatform`, this library ensures the project can be compiled without requiring any HTTP stack implementation. This is useful if you want to use `az_core` without `az_http` functionality.

Expand Down
2 changes: 1 addition & 1 deletion sdk/docs/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The library allows client libraries to expose common functionality in a consiste

## Porting the Azure SDK to Another Platform

The `Azure Core` library requires you to implement a few functions to provide platform-specific features such as a clock and thread sleep. By default, `Azure Core` ships with no-op versions of these functions, all of which return 0 or do no operations. The no-op versions allow the Azure SDK to compile successfully so you can verify that your build tool chain is working properly; however, failures may occur if you execute the code.
The `Azure Core` library requires you to implement a few functions to provide platform-specific features such as a clock and thread sleep. By default, `Azure Core` ships with no-op versions of these functions, all of which return `AZ_ERROR_DEPENDENCY_NOT_PROVIDED`. These function versions allow the Azure SDK to compile successfully so you can verify that your build tool chain is working properly; however, failures may occur if you execute the code.

## Key Concepts

Expand Down
2 changes: 1 addition & 1 deletion sdk/docs/platform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The Azure SDK platform layer provides abstractions for platform specific APIs wh

## Platform

Azure SDK Core depends on some system-specific functions. These functions are not part of the C99 standard library and their implementation depends on system architecture (for example a clock, thread sleep, or interlock).
Azure SDK Core depends on some system-specific functions. These functions are not part of the C99 standard library and their implementation depends on system architecture (for example, clock, or thread sleep).

Azure SDK provides three platform implementations for you, one for Windows (`az_win32`), another for Linux and MacOS (`az_posix`) and an empty implementation (`az_noplatform`).

Expand Down
2 changes: 2 additions & 0 deletions sdk/inc/azure/core/az_http_transport.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ AZ_NODISCARD int32_t az_http_request_headers_count(az_http_request const* reques
* @retval #AZ_ERROR_HTTP_RESPONSE_COULDNT_RESOLVE_HOST The URL from \p ref_request can't be
* resolved by the HTTP stack and the request was not sent.
* @retval #AZ_ERROR_HTTP_ADAPTER Any other issue from the transport adapter layer.
* @retval #AZ_ERROR_DEPENDENCY_NOT_PROVIDED No platform implementation was supplied to support this
* function.
*/
AZ_NODISCARD az_result
az_http_client_send_request(az_http_request const* request, az_http_response* ref_response);
Expand Down
16 changes: 13 additions & 3 deletions sdk/inc/azure/core/az_platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,14 @@
* called twice with one second interval, the difference between the values returned should be equal
* to 1000.
*
* @return Platform clock in milliseconds.
* @param[out] out_clock_msec Platform clock in milliseconds.
*
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK Success.
* @retval #AZ_ERROR_DEPENDENCY_NOT_PROVIDED No platform implementation was supplied to support this
* function.
*/
AZ_NODISCARD int64_t az_platform_clock_msec();
AZ_NODISCARD az_result az_platform_clock_msec(int64_t* out_clock_msec);

/**
* @brief Tells the platform to sleep for a given number of milliseconds.
Expand All @@ -40,8 +45,13 @@ AZ_NODISCARD int64_t az_platform_clock_msec();
*
* @remarks The behavior is undefined when \p milliseconds is a non-positive value (0 or less than
* 0).
*
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK Success.
* @retval #AZ_ERROR_DEPENDENCY_NOT_PROVIDED No platform implementation was supplied to support this
* function.
*/
void az_platform_sleep_msec(int32_t milliseconds);
AZ_NODISCARD az_result az_platform_sleep_msec(int32_t milliseconds);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is an unnecessary burden to the implementor of the PAL.

Copy link
Member

Choose a reason for hiding this comment

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

@ahsonkhan are you referring to the AZ_NODISCARD or the az_result. I agree that we shouldn't force AZ_NODISCARD onto the implementations. I however do prefer the az_result return codes on the PAL. The minimal burden is greatly outweighed by the fact we can consistently validate the API performed as expected

Copy link
Contributor

@ahsonkhan ahsonkhan Sep 11, 2020

Choose a reason for hiding this comment

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

are you referring to the AZ_NODISCARD or the az_result

Both.

The minimal burden is greatly outweighed by the fact we can consistently validate the API performed as expected

I don't understand what you mean by this. What are we getting here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ha, what I was thinking of, is that you Ahson was talking about the contrast of this variant to the proposal in #1257, saying that out parameters and az_result are generally more cumbersome to implement. I didn't read it as if you are commenting on the AZ_NODISCARD.
Regarding AZ_NODISCARD, I do not think it is a burden on the implementor. It is an intentional burden on the user, i.e. us. So, if we ignore the return result, we get a warning treated as compilation error. For the implementor, I am not sure if they even have to put AZ_NODISCARD in the signature of their imlpementation - having it in this header alone is probably enough (I am not sure, because AZ_NODISCARD is really compiler-specific).

Copy link
Member

Choose a reason for hiding this comment

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

OMHO, platform should not depend on az_core. Only az_core should depend on some specific implementation.
Right now, in order to implement a platform, people need to get az_core, and people getting az_core need an az_platform. I don't like how that look XD. It works using cmake targets, but not sure if it would work without it (including pah directly)

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting point on layering the dependency @vhvb1989


#include <azure/core/_az_cfg_suffix.h>

Expand Down
10 changes: 6 additions & 4 deletions sdk/src/azure/core/az_http_policy_logging.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,7 @@ static az_result _az_http_policy_logging_append_http_request_msg(
remainder = az_span_copy(remainder, new_line_tab_string);
remainder = az_span_copy(remainder, header_name);

if (az_span_size(header_value) > 0
&& !az_span_is_content_equal(header_name, auth_header_name))
if (az_span_size(header_value) > 0 && !az_span_is_content_equal(header_name, auth_header_name))
{
remainder = az_span_copy(remainder, colon_separator_string);
remainder = _az_http_policy_logging_copy_lengthy_value(remainder, header_value);
Expand Down Expand Up @@ -254,10 +253,13 @@ AZ_NODISCARD az_result az_http_pipeline_policy_logging(
return _az_http_pipeline_nextpolicy(ref_policies, ref_request, ref_response);
}

int64_t const start = az_platform_clock_msec();
int64_t start;
_az_RETURN_IF_FAILED(az_platform_clock_msec(&start));

az_result const result = _az_http_pipeline_nextpolicy(ref_policies, ref_request, ref_response);
int64_t const end = az_platform_clock_msec();

int64_t end;
_az_RETURN_IF_FAILED(az_platform_clock_msec(&end));
_az_http_policy_logging_log_http_response(ref_response, end - start, ref_request);

return result;
Expand Down
11 changes: 8 additions & 3 deletions sdk/src/azure/core/az_http_policy_retry.c
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,16 @@ AZ_NODISCARD az_result az_http_pipeline_policy_retry(
_az_http_policy_retry_log(attempt, retry_after_msec);
}

az_platform_sleep_msec(retry_after_msec);
_az_RETURN_IF_FAILED(az_platform_sleep_msec(retry_after_msec));

if (context != NULL && az_context_has_expired(context, az_platform_clock_msec()))
if (context != NULL)
{
return AZ_ERROR_CANCELED;
int64_t clock = 0;
_az_RETURN_IF_FAILED(az_platform_clock_msec(&clock));
if (az_context_has_expired(context, clock))
{
return AZ_ERROR_CANCELED;
}
}
}

Expand Down
7 changes: 0 additions & 7 deletions sdk/src/azure/platform/az_curl.c
Original file line number Diff line number Diff line change
Expand Up @@ -590,13 +590,6 @@ static AZ_NODISCARD az_result _az_http_client_curl_send_request_impl_process(
return result;
}

/**
* @brief uses AZ_HTTP_BUILDER to set up CURL request and perform it.
*
* @param request an internal http builder with data to build and send http request
* @param ref_response pre-allocated buffer where http response will be written
* @return az_result
*/
AZ_NODISCARD az_result
az_http_client_send_request(az_http_request const* request, az_http_response* ref_response)
{
Expand Down
9 changes: 0 additions & 9 deletions sdk/src/azure/platform/az_nohttp.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,6 @@

#include <azure/core/_az_cfg.h>

/**
* @brief Provides no HTTP support.
*
* @param request An internal HTTP builder with data to build and send HTTP request.
* @param ref_response A pre-allocated buffer where the HTTP response will be written.
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK Success.
* @retval other Failure.
*/
AZ_NODISCARD az_result
az_http_client_send_request(az_http_request const* request, az_http_response* ref_response)
{
Expand Down
14 changes: 12 additions & 2 deletions sdk/src/azure/platform/az_noplatform.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@
// SPDX-License-Identifier: MIT

#include <azure/core/az_platform.h>
#include <azure/core/internal/az_precondition_internal.h>

#include <azure/core/_az_cfg.h>

AZ_NODISCARD int64_t az_platform_clock_msec() { return 0; }
AZ_NODISCARD az_result az_platform_clock_msec(int64_t* out_clock_msec)
{
_az_PRECONDITION_NOT_NULL(out_clock_msec);
*out_clock_msec = 0;
return AZ_ERROR_DEPENDENCY_NOT_PROVIDED;
}

void az_platform_sleep_msec(int32_t milliseconds) { (void)milliseconds; }
AZ_NODISCARD az_result az_platform_sleep_msec(int32_t milliseconds)
Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at this again, I agree with Rick on the sleep API, we should consider removing AZ_NODISCARD.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think we should. It is us who calls these APIs. Rick's assumption is that 3 things are happening: 1. Customer calls PAL function. 2. Doesn't handle a result, and doesn't need/want to. 3. It is painful to ignore the result, which I think is not:

az_result ignore = az_platform_sleep_msec(1000);
(void)ignore;

Copy link
Contributor

@ahsonkhan ahsonkhan Sep 17, 2020

Choose a reason for hiding this comment

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

In that case, we should consider updating the documentation of az_http_pipeline_policy_logging and az_http_pipeline_policy_retry since they can now return any PAL-defined az_result. Alternatively, those APIs should take any PAL-error and convert it to a single error value, and return/document that.

{
(void)milliseconds;
return AZ_ERROR_DEPENDENCY_NOT_PROVIDED;
}
12 changes: 8 additions & 4 deletions sdk/src/azure/platform/az_posix.c
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT

#include <azure/core/internal/az_config_internal.h>
#include <azure/core/az_platform.h>
#include <azure/core/internal/az_config_internal.h>
#include <azure/core/internal/az_precondition_internal.h>

#include <time.h>

#include <unistd.h>

#include <azure/core/_az_cfg.h>

AZ_NODISCARD int64_t az_platform_clock_msec()
AZ_NODISCARD az_result az_platform_clock_msec(int64_t* out_clock_msec)
{
return (int64_t)((clock() / CLOCKS_PER_SEC) * _az_TIME_MILLISECONDS_PER_SECOND);
_az_PRECONDITION_NOT_NULL(out_clock_msec);
*out_clock_msec = (int64_t)((clock() / CLOCKS_PER_SEC) * _az_TIME_MILLISECONDS_PER_SECOND);
return AZ_OK;
}

void az_platform_sleep_msec(int32_t milliseconds)
AZ_NODISCARD az_result az_platform_sleep_msec(int32_t milliseconds)
{
(void)usleep((useconds_t)milliseconds * _az_TIME_MICROSECONDS_PER_MILLISECOND);
Copy link
Contributor

Choose a reason for hiding this comment

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

unrelated: precondition that milliseconds is positive.

Copy link
Member Author

Choose a reason for hiding this comment

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

So far we say in the docs, @remarks The behavior is undefined when \p milliseconds is a non-positive value (0 or less than 0). That's because on some platform, it would yield to other thread, but will return control back if no other thread needs it. We don't use this feature though.

Copy link
Contributor

@ahsonkhan ahsonkhan Sep 11, 2020

Choose a reason for hiding this comment

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

That's because on some platform, it would yield to other thread, but will return control back if no other thread needs it. We don't use this feature though.

We can add a precondition now to help folks write correct code, and loosen that in the future, if that value becomes meaningful.

Copy link
Member

Choose a reason for hiding this comment

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

That's because on some platform, it would yield to other thread, but will return control back if no other thread needs it. We don't use this feature though.

We can add a precondition now to help folks write correct code, and loosen that in the future, if that value becomes meaningful.

We could precondition, but we would be limiting to a particular implementation of POSIX. Its not clear if every implementation places the same constraints. e.g. some platforms say the usec is not smaller than 1000000 others allow 0 to mean yield.

Copy link
Contributor

@ahsonkhan ahsonkhan Sep 11, 2020

Choose a reason for hiding this comment

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

This is one particular implementation though. Adding a precondition here doesn't impact users who want to provide their own PAL implementation. Passing in a negative millisecond will overflow when cast to unsigned int, and that could be an unintended bug in the user's code (given usleep takes an unsigned number).
https://man7.org/linux/man-pages/man3/usleep.3.html#NOTES

return AZ_OK;
}
14 changes: 12 additions & 2 deletions sdk/src/azure/platform/az_win32.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: MIT

#include <azure/core/az_platform.h>
#include <azure/core/internal/az_precondition_internal.h>

// Two macros below are not used in the code below, it is windows.h that consumes them.
#define WIN32_LEAN_AND_MEAN
Expand All @@ -10,6 +11,15 @@

#include <azure/core/_az_cfg.h>

AZ_NODISCARD int64_t az_platform_clock_msec() { return GetTickCount64(); }
AZ_NODISCARD az_result az_platform_clock_msec(int64_t* out_clock_msec)
{
_az_PRECONDITION_NOT_NULL(out_clock_msec);
*out_clock_msec = GetTickCount64();
return AZ_OK;
}

void az_platform_sleep_msec(int32_t milliseconds) { Sleep(milliseconds); }
AZ_NODISCARD az_result az_platform_sleep_msec(int32_t milliseconds)
{
Sleep(milliseconds);
Copy link
Contributor

Choose a reason for hiding this comment

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

I still think we should add a _az_PRECONDITION(milliseconds > 0) to all the implementations of the PAL.

Copy link
Member Author

@antkmsft antkmsft Sep 17, 2020

Choose a reason for hiding this comment

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

Since we have a variety of opinions, let's cover it outside of the scope of this PR, it can be added later without affecting this change.

return AZ_OK;
}
2 changes: 1 addition & 1 deletion sdk/tests/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ include(AddTestCMocka)

# -ld link option is only available for gcc
if(UNIT_TESTING_MOCKS)
set(WRAP_FUNCTIONS "-Wl,--wrap=az_platform_clock_msec")
set(WRAP_FUNCTIONS "-Wl,--wrap=az_platform_clock_msec -Wl,--wrap=az_platform_sleep_msec")
else()
set(WRAP_FUNCTIONS "")
endif()
Expand Down
27 changes: 20 additions & 7 deletions sdk/tests/core/test_az_policy.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <azure/core/az_http_transport.h>
#include <azure/core/az_span.h>
#include <azure/core/internal/az_http_internal.h>
#include <azure/core/internal/az_precondition_internal.h>

#include <setjmp.h>
#include <stdarg.h>
Expand Down Expand Up @@ -90,7 +91,7 @@ void test_az_http_pipeline_policy_telemetry(void** state)
// Create policy options
_az_http_policy_telemetry_options telemetry = _az_http_policy_telemetry_options_default();

_az_http_policy policies[1] = {
_az_http_policy policies[1] = {
{
._internal = {
.process = test_policy_transport,
Expand Down Expand Up @@ -135,7 +136,7 @@ void test_az_http_pipeline_policy_apiversion(void** state)
api_version._internal.name = AZ_SPAN_FROM_STR("name");
api_version._internal.version = AZ_SPAN_FROM_STR("version");

_az_http_policy policies[1] = {
_az_http_policy policies[1] = {
{
._internal = {
.process = test_policy_transport,
Expand Down Expand Up @@ -248,7 +249,7 @@ void test_az_http_pipeline_policy_retry(void** state)
// Create policy options
az_http_policy_retry_options retry_options = _az_http_policy_retry_options_default();

_az_http_policy policies[1] = {
_az_http_policy policies[1] = {
{
._internal = {
.process = test_policy_transport_retry_response,
Expand Down Expand Up @@ -295,7 +296,7 @@ void test_az_http_pipeline_policy_retry_with_header(void** state)
// make just one retry
retry_options.max_retries = 1;

_az_http_policy policies[1] = {
_az_http_policy policies[1] = {
{
._internal = {
.process = test_policy_transport_retry_response_with_header,
Expand Down Expand Up @@ -342,7 +343,7 @@ void test_az_http_pipeline_policy_retry_with_header_2(void** state)
// make just one retry
retry_options.max_retries = 1;

_az_http_policy policies[1] = {
_az_http_policy policies[1] = {
{
._internal = {
.process = test_policy_transport_retry_response_with_header_2,
Expand All @@ -358,8 +359,20 @@ void test_az_http_pipeline_policy_retry_with_header_2(void** state)
az_http_pipeline_policy_retry(policies, &retry_options, &request, &response), AZ_OK);
}

int64_t __wrap_az_platform_clock_msec();
int64_t __wrap_az_platform_clock_msec() { return (int64_t)mock(); }
az_result __wrap_az_platform_clock_msec(int64_t* out_clock_msec);
az_result __wrap_az_platform_clock_msec(int64_t* out_clock_msec)
{
_az_PRECONDITION_NOT_NULL(out_clock_msec);
*out_clock_msec = (int64_t)mock();
return AZ_OK;
}

az_result __wrap_az_platform_sleep_msec(int32_t milliseconds);
az_result __wrap_az_platform_sleep_msec(int32_t milliseconds)
{
(void)milliseconds;
return AZ_OK;
}

#endif // _az_MOCK_ENABLED

Expand Down