-
Notifications
You must be signed in to change notification settings - Fork 14
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
Implement multi-block upload #747
base: szczys/multi-block-upload
Are you sure you want to change the base?
Conversation
5a588a4
to
3aa76d1
Compare
Visit the preview URL for this PR (updated for commit a3ed77d): https://golioth-firmware-sdk-doxygen-dev--pr747-szczys-impleme-7rd9tksg.web.app (expires Sat, 22 Feb 2025 18:52:15 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: a9993e61697a3983f3479e468bcb0b616f9a0578 |
061e018
to
4024723
Compare
4024723
to
c827d51
Compare
src/coap_blockwise.h
Outdated
/* Blockwise Multi-Part Upload */ | ||
|
||
// Note: callback_arg will be the same for all blocks | ||
struct blockwise_transfer_multi *golioth_blockwise_multi_upload_ctx_create( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
multi_upload
is confusing here, it can imply that we're uploading multiple distinct pieces of data instead of a single piece of data in parts. A generic term for this kind of API is "multipart", and the CoAP terminology is "blockwise", but a multipart API isn't typically explicitly called multipart in the function names. Additionally, the ctx
bit is implied by the return type, we don't need it in the function name. All of which is to say, I don't think we need to explicitly call this "multi", this is just an API for doing blockwise uploads.
struct blockwise_transfer_multi *golioth_blockwise_multi_upload_ctx_create( | |
struct blockwise_transfer *golioth_blockwise_upload_create(...); | |
int golioth_blockwise_upload_post_block(struct blockwise_transfer *transfer, ...); | |
void golioth_blockwise_upload_destroy(struct blockwise_transfer *transfer); |
Finally, start()
and finish()
might be clearer than create()
/destroy()
. Either works for me if you feel strongly, but I lean towards start/finish over create/destroy because start/finish makes it clear that the context is only valid for one transaction, while create/destroy typically indicates that the object may be used repeatedly for the life of the program (like a mutex or semaphore).
include/golioth/stream.h
Outdated
/// Multi-block transfer context | ||
/// | ||
/// All memory is owned by user and must persist until the final block is sent | ||
struct blockwise_transfer_multi; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be defined in coap_blockwise.h
, otherwise only the stream service will be able to use the blockwise API.
src/coap_blockwise.c
Outdated
#include <assert.h> | ||
#include "coap_client.h" | ||
#include "coap_blockwise.h" | ||
|
||
LOG_TAG_DEFINE(golioth_coap_blockwise); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's match the filename and save a few bytes.
LOG_TAG_DEFINE(golioth_coap_blockwise); | |
LOG_TAG_DEFINE(coap_blockwise); |
src/coap_blockwise.c
Outdated
memset(ctx->path, 0, sizeof(ctx->path)); | ||
memcpy(ctx->path, path, strlen(path)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can just use a single strncpy()
call instead of these two calls.
src/coap_blockwise.c
Outdated
@@ -283,6 +311,72 @@ enum golioth_status golioth_blockwise_post(struct golioth_client *client, | |||
return status; | |||
} | |||
|
|||
// Create an async upload context | |||
struct blockwise_transfer_multi *golioth_blockwise_multi_upload_ctx_create( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we increase code re-use between these new functions and the existing single shot API? The single shot API should be able to use these functions for part of its implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good call-out, thanks!
I added a commit that breaks out almost all initialization into the existing blockwise_upload_init()
and also added a blockwise_upload_free()
. This way, all block upload functions can use these two functions.
There were a couple of choices made as part of this:
- The existing sync function guarantees
path
is not freed before the the block download completes, but this new multipart approach can't make that guarantee. I addedchar *stored_path
as a context member and store the path there when needed - I have a bit of angst about
path_prefix
. This is only used internally to the SDK and we pass a#define
constant. (But that's not enforced or documented.) As long as we continue this practice and don't pass a prefix from the user API we avoid use-after-free issues. I left this as is. - The two upload approaches use different callback types. I removed callback (and callback_arg) from the init function. These should be set by the caller (if desired) after initialization.
src/coap_blockwise.c
Outdated
@@ -39,6 +42,15 @@ struct blockwise_transfer | |||
void *callback_arg; | |||
}; | |||
|
|||
struct blockwise_transfer_multi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's increase code re-use by including this in the context for the single shot API instead of duplicating fields. All of these fields are relevant to a blockwise transfer, whether the API is single shot or multipart.
src/coap_blockwise.c
Outdated
@@ -39,6 +42,15 @@ struct blockwise_transfer | |||
void *callback_arg; | |||
}; | |||
|
|||
struct blockwise_transfer_multi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is the public facing struct, let's keep it simple and clear for users, and rename the context struct for the single shot API.
struct blockwise_transfer_multi | |
struct blockwise_transfer |
82b3e6c
to
92d0f89
Compare
Extend `blockwise_upload_init()` the handle memory allocation and return a pointer to an initialized context. Add `blockwise_upload_free()` to free dynamically allocated context. The init function no longer takes a callback/callback_arg. This is because `struct blockwise_transfer` uses a union for the callback member. The appropriate callback type should be set by the caller after initializing the context. Signed-off-by: Mike Szczys <[email protected]>
Caller creates and holds `blockwise_transfer`, supplying path, content_type, and optional callback and callback_arg as parameters. Each block is then sent by repeatedly calling `golioth_blockwise_upload_block()`. The context can be destroyed after the final blocks have been received by the server. Signed-off-by: Mike Szczys <[email protected]>
- Call `golioth_stream_set_blockwise_start()` to create a context. - Call `golioth_stream_set_blockwise_send()` for each block - Call `golioth_stream_set_blockwise_stop()` to destroy the context. Signed-off-by: Mike Szczys <[email protected]>
Signed-off-by: Mike Szczys <[email protected]>
92d0f89
to
a3ed77d
Compare
Codecov ReportAttention: Patch coverage is
|
Multi-block upload implements a blockwise upload using repeated API calls: