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

Feature/c api #57

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open

Feature/c api #57

wants to merge 12 commits into from

Conversation

ChrisspyB
Copy link
Member

No description provided.

@codecov-commenter
Copy link

codecov-commenter commented Dec 11, 2024

Codecov Report

Attention: Patch coverage is 74.90909% with 69 lines in your changes missing coverage. Please review.

Project coverage is 61.11%. Comparing base (963c295) to head (bf9f488).

Files with missing lines Patch % Lines
src/metkit/api/metkit_c.cc 64.79% 69 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop      #57      +/-   ##
===========================================
+ Coverage    60.39%   61.11%   +0.71%     
===========================================
  Files          101      103       +2     
  Lines         6242     6517     +275     
  Branches       585      590       +5     
===========================================
+ Hits          3770     3983     +213     
- Misses        2472     2534      +62     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ChrisspyB ChrisspyB requested a review from tbkr December 16, 2024 09:43
@ChrisspyB ChrisspyB marked this pull request as ready for review December 16, 2024 09:43
Copy link
Member Author

Choose a reason for hiding this comment

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

Todo: Ensure we cover all functionality currently in fdb_c so that we can replace it with this.

Copy link

Choose a reason for hiding this comment

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

Looks good so far. The functionality of the pyfdb is the following:

struct fdb_request_t;
typedef struct fdb_request_t fdb_request_t;
int fdb_new_request(fdb_request_t** req);
int fdb_request_add(fdb_request_t* req, const char* param, const char* values[], int numValues);
int fdb_expand_request(fdb_request_t* req);
int fdb_delete_request(fdb_request_t* req);

Those are all contained in the request section. Do we need anything on top @ChrisspyB?

Copy link

@tbkr tbkr left a comment

Choose a reason for hiding this comment

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

Like the changes. Code and Documentation was extraordinarily clean. I was a bit nitpicky. Bear with me in case some of my suggestions are not compatible with modern C; it has been a while since I programmed in C ;)

src/metkit/api/metkit_c.cc Outdated Show resolved Hide resolved
src/metkit/api/metkit_c.cc Outdated Show resolved Hide resolved
/// @comment: (maby)
/// Not sure if there is much value in having a param iterator. We could just return an array of
/// strings (char**) using metkit_marsrequest_params.
/// I think we should metkit_paramiterator_t.
Copy link

Choose a reason for hiding this comment

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

Here the comment is lacking some words. Do this need to be addressed before merging?

Copy link
Member Author

Choose a reason for hiding this comment

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

a todo left for me by me

src/metkit/api/metkit_c.cc Outdated Show resolved Hide resolved

} // extern "C"

static thread_local std::string g_current_error_string;
Copy link

Choose a reason for hiding this comment

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

This is read-only, isn't it? Make it const.

src/metkit/api/metkit_c.cc Outdated Show resolved Hide resolved
tests/test_c_api.cc Outdated Show resolved Hide resolved
} while (false)

// Fairly minimal test coverage
CASE( "metkit_marsrequest" ) {
Copy link

Choose a reason for hiding this comment

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

This test is quite long. Could you break it in individual tests for easier and faster comprehension?

*/
int metkit_marsrequest_add(metkit_marsrequest_t* request, const char* param, const char* values[], int numValues);

/** Add parameter and values to request
Copy link

Choose a reason for hiding this comment

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

Would be nice to have some documentation on why the 1 appears in the function name. Or even better: renaming to function to reflect what is happening.

Copy link

Choose a reason for hiding this comment

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

Looks good so far. The functionality of the pyfdb is the following:

struct fdb_request_t;
typedef struct fdb_request_t fdb_request_t;
int fdb_new_request(fdb_request_t** req);
int fdb_request_add(fdb_request_t* req, const char* param, const char* values[], int numValues);
int fdb_expand_request(fdb_request_t* req);
int fdb_delete_request(fdb_request_t* req);

Those are all contained in the request section. Do we need anything on top @ChrisspyB?

* @param requests Allocates RequestIterator object
* @return int Error code
*/
int metkit_parse_marsrequest(const char* str, metkit_requestiterator_t** requests, bool strict = false);
Copy link
Contributor

Choose a reason for hiding this comment

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

This returns mars requests (plural), but in the function name it is only singular. That isn't cool.

Also, please make sure you run this through a C compiler --- NOT a C++ compiler. Note that C does not support default arguments.

Copy link
Contributor

Choose a reason for hiding this comment

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

And you will need to include <stdbool.h> if you want to use bool - as bool is not a builtin type in C.

src/metkit/api/metkit_c.h Outdated Show resolved Hide resolved
src/metkit/api/metkit_c.h Outdated Show resolved Hide resolved
* @param params Allocates ParamIterator object for parameter names in request
* @return int Error code
*/
int metkit_marsrequest_params(const metkit_marsrequest_t* request, metkit_paramiterator_t** params);
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be easier to return the number of params, and then access them by index? Avoid creating/disposing of the paramiterator?

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 agree.

* @param numValues number of values for param in request
* @return int Error code
*/
int metkit_marsrequest_values(const metkit_marsrequest_t* request, const char* param, const char** values[], size_t* numValues);
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this allocate an array of values in C and then return it? Who is responsible for cleaning up that array of values? Why is this done in a different way to accessing the parameters?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes this makes an array of values that the calling code is responsible for cleaning up. Thoughts on that? I was thinking we could do the same with parameters.

Copy link

Choose a reason for hiding this comment

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

Freeing 2d allocations in C is a bit verbose. I suggest either create an opaque type and implement the required query functions or use a public structure that can be returned as handle and has a corresponding free function.


Opaque type

struct marsrequest_values_t;
typedef marsrequest_values_t* marsrequest_values_handle;
size_t marsrequest_values_size(marsrequest_values_handle h);
const char* marsrequest_values_size(size_t idx);
void marsrequest_values_free(marsrequest_values_handle h);

Public type

typedef struct {
    size_t size;
    const char** values
} marsrequest_values;

void marsrequest_values_free(marsrequest_values* values);

I think an opaque type would be nice as this could simply be a std::vector<string> on the C++ side.

Copy link

@Ozaq Ozaq left a comment

Choose a reason for hiding this comment

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

You need an extra test where you use the API and compile the test with a C compiler

typedef enum metkit_error_values_t
{
METKIT_SUCCESS = 0, /* Operation succeded. */
METKIT_ITERATION_COMPLETE = 1, /* All elements have been returned */
Copy link

Choose a reason for hiding this comment

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

This is not an error and should be handled differently. Right now this mixes error and result states. I would advocate for a strict separation.

src/metkit/api/metkit_c.h Outdated Show resolved Hide resolved
* @param version Version string
* @return int Error code
*/
int metkit_version(const char** version);
Copy link

Choose a reason for hiding this comment

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

This call should not be able to fail and therefor just return the version

Copy link
Member Author

Choose a reason for hiding this comment

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

One convience from every function returning an error code is that it allows us (e.g. on the python layer) to wrap every C function call with a simple error handler that just checks the return value.

* @param sha1 SHA1 version string
* @return int Error code
*/
int metkit_vcs_version(const char** sha1);
Copy link

Choose a reason for hiding this comment

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

This call should not be able to fail and therefor just return the sha1

*
* @note This is ONLY required when Main() is NOT initialised, such as loading
* the MetKit as shared library in Python.
* @return int Error code
Copy link

Choose a reason for hiding this comment

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

This should document the possible error codes returned from this function and under which circumstances the errors are to be expected.

The underlying issue here is that every possible error that can happen when using this API is corralled into metkit_error_values_t. When using such an API you will greatly thank the doc writer for stating the possible error cases, (think man pages for example) because this allows me to simplify error handling. And treat every unexpected EC as fatal error.

If the user is not given any information about the possible errors, he will always be wondering "Could this return a METKIT_ITERATION_COMPLETE?"

* @param it RequestIterator instance
* @return int Error code
*/
int metkit_requestiterator_next(metkit_requestiterator_t* it);
Copy link

Choose a reason for hiding this comment

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

I think this would make for a nicer API if we could use it like this:

while((res = metkit_paramiterator_next(it))) {
   //...
}

Ofc this would only work if the iteration cannot create errors. But return a nullptr on end of iteration makes for a more natural api imo.

src/metkit/api/metkit_c.h Outdated Show resolved Hide resolved
* @param numValues number of values for param in request
* @return int Error code
*/
int metkit_marsrequest_values(const metkit_marsrequest_t* request, const char* param, const char** values[], size_t* numValues);
Copy link

Choose a reason for hiding this comment

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

Freeing 2d allocations in C is a bit verbose. I suggest either create an opaque type and implement the required query functions or use a public structure that can be returned as handle and has a corresponding free function.


Opaque type

struct marsrequest_values_t;
typedef marsrequest_values_t* marsrequest_values_handle;
size_t marsrequest_values_size(marsrequest_values_handle h);
const char* marsrequest_values_size(size_t idx);
void marsrequest_values_free(marsrequest_values_handle h);

Public type

typedef struct {
    size_t size;
    const char** values
} marsrequest_values;

void marsrequest_values_free(marsrequest_values* values);

I think an opaque type would be nice as this could simply be a std::vector<string> on the C++ side.

tests/test_c_api.cc Outdated Show resolved Hide resolved
tests/test_c_api.cc Outdated Show resolved Hide resolved
src/metkit/api/metkit_c.cc Outdated Show resolved Hide resolved
/// Not sure if there is much value in having a param iterator. We could just return an array of
/// strings (char**) using metkit_marsrequest_params.
/// I think we should metkit_paramiterator_t.
struct metkit_paramiterator_t {
Copy link

Choose a reason for hiding this comment

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

Lifetimes of returned values in this iterator differ from the metkit_requestiterator_t iterator. For itself the lifetime is ok (if documented) but a subtle difference in lifetime handling with both types imply symmetrical behavior is a nasty trap. metkit_requestiterator_t result lifetimes exceed the iterator due to the move out of the iterator, while the char* from this iterator dangle as soon as the iterator is freed.

src/metkit/api/metkit_c.cc Outdated Show resolved Hide resolved
src/metkit/api/metkit_c.cc Outdated Show resolved Hide resolved
src/metkit/api/metkit_c.cc Outdated Show resolved Hide resolved
return METKIT_ERROR_USER;
}
catch (const eckit::AssertionFailed& e) {
eckit::Log::error() << "Assertion Failed: " << e.what() << std::endl;
Copy link

Choose a reason for hiding this comment

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

I think the c api wrapper should not log errors, this is imo a responsibility of metkit itself -OR- the calling code.

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'm inclined to agree

Comment on lines +131 to +139
int metkit_version(const char** version) {
*version = metkit_version_str();
return METKIT_SUCCESS;
}

int metkit_vcs_version(const char** sha1) {
*sha1 = metkit_git_sha1();
return METKIT_SUCCESS;
}
Copy link

Choose a reason for hiding this comment

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

So since its always METKIT_SUCCESS this should just return the const char*

src/metkit/api/metkit_c.cc Outdated Show resolved Hide resolved
src/metkit/api/metkit_c.cc Outdated Show resolved Hide resolved
src/metkit/api/metkit_c.cc Outdated Show resolved Hide resolved
src/metkit/api/metkit_c.cc Outdated Show resolved Hide resolved
@ChrisspyB
Copy link
Member Author

ChrisspyB commented Dec 20, 2024

You need an extra test where you use the API and compile the test with a C compiler

Maybe we should rewrite test_metkit_c.cc in C? For the time being I'll add a simple test that at least uses a C compiler

@ChrisspyB
Copy link
Member Author

Re: error handling, if we're going to do similar across the stack, it might make sense to centralise this somewhere (eckit). All of the caught exceptions are in fact eckit::exceptions

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.

6 participants