-
Notifications
You must be signed in to change notification settings - Fork 260
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
initial version of plugin based storage api #7
Conversation
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.
Do you expect the StorageInterface
to have more functions in the future (e.g. seek
, read
or get_next
, etc...)? Or will it be a different interface that operates on the StorageHandle
?
Currently the StorageHandle
class has no efficient way to compare itself to open bags, I imagine close
must just string compare to find out, which might be ok for close, but if you need to do this to get the next message, then that's way too slow. Basically how are you going to do data association with the handle and the actual object that does the work inside of the StorageInterface
(assuming that is what will operate on the open bags).
I'm also not sure about the naming, why is it rosbag2_storage::StorageHandle
and not rosbag2_storage::Handle
or rosbag2_storage::RosbagStorageHandle
? The first alternative avoids any redundant information and the second one, while redundant, is identifiable without the namespace (StorageHandle
without the namespace is pretty generic).
How will the user determine which format to use? Let's say the file path is file:///path/to/my_bag
(no extension) and the factory can produce an interface for SQLite3 and ROS 1 bag. Is there some mechanism for storage backends to look at a file and say whether or not it's a valid one for that plugin (assuming the extension is wrong or missing)?
I was imagining that there would be a function like std::shared_ptr<StorageInterface> get_storage_interface_for_bag(std::string & bag_url)
, which would either return an instance of StorageInterface
that works for that bag or raise an exception (or return an error code?) if none of the backends work with that file. How the implementations determine that is an implementation detail (doesn't matter), but the storage API or the user shouldn't have to figure out what backend if any should be used with a given file (otherwise it would need to know about all possible file type <-> plugin mappings ahead of time).
It should still be possible to try and force it to use a particular backend with a file (maybe for testing and corner cases), but the typical case should likely let the backends decide if a file can be opened or not.
*/ | ||
ROSBAG2_STORAGE_PUBLIC | ||
std::shared_ptr<StorageInterface> | ||
get_storage_interface(); |
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.
Shouldn't this function take a storage identifier? Otherwise it will never be able to return a specific implementation of the interface right? It's also documented in the doc block above.
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.
The factory instance gets the identifier and thus get_storage_interface()
shall only return one specific type of interface. But you're right, that might be misleading. We could maybe template the class with the interface type to have it a strongly typed factory?
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.
Well at least I would update the documentation, since it says that this function takes the identifier. I guess a template argument is ok. I don't have the context to say if it's required/desired or not.
typedef struct ROSBAG2_STORAGE_PUBLIC_TYPE StorageHandle | ||
{ | ||
std::string storage_identifier; | ||
std::string file_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.
What is the file_path
here? Might want to document this class and it's members.
/// Open the bag file given the specified path | ||
/** | ||
* The function opens the bag file and returns a handle to it. | ||
* \param file_path the fqdn path to the bag to be loaded |
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.
fqdn
doesn't make sense when talking about file paths... It stands for "Fully qualified domain name".
ROSBAG2_STORAGE_PUBLIC | ||
virtual | ||
void | ||
set_storage_identifier(const std::string & storage_identifier) = 0; |
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.
Should this be a public method? Can you explain how you expect it to be used and by who (users versus implementers of the storage interface)?
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.
auto instance = class_loader_->createSharedInstance(storage_identifier_);
instance->set_storage_identifier(storage_identifier_);
return instance;
Normally, I would set the storage_identifier
directly at the constructor level. However, as every plugin gets loaded with pluginlib and thus requires a default constructor, I'd need a way of setting this identifier. I could put the identifier as a public member field, but I feel better about having this setter to avoid any external modification of this field.
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.
Normally, I would set the storage_identifier directly at the constructor level. However, as every plugin gets loaded with pluginlib and thus requires a default constructor, I'd need a way of setting this identifier. I could put the identifier as a public member field, but I feel better about having this setter to avoid any external modification of this field.
Why is the storage identifier being put into the plugin? Shouldn't the plugin know what its identifier is?
ROSBAG2_STORAGE_PUBLIC | ||
virtual | ||
StorageHandle | ||
open(const std::string & file_path) = 0; |
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 probably fine for now, but in the future you might need to specify "bags" in a more complex way, e.g. it could be a folder, or a list of files, or a single file which contains multiple named "bags", or a URL to database (just imagining extremes), etc... Basically I'm not sure the assumption that a bag is uniquely identified by a single file path is going to scale. But it should be ok for now.
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.
I'd say a parameter uri
fits better since it can be used to describe any resource, not just files.
Thanks @wjwwood for the feedback. I'll address the comments ASAP. You're right! This is early stage and not quite ready for review. |
Does that also apply then to |
Yeah, I would personally choose |
b2e20ac
to
4da345a
Compare
|
||
struct SerializedBagMessage | ||
{ | ||
rcutils_char_array_t serialized_data; |
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.
In principle, this is what I would've expected - except perhaps a shared_ptr<rcutils_char_array_t>
because that seems to be the type we get in a subscription callback.
However, I have two questions:
a) The original design idea was to have librosbag2_storage independent of ros2. This part here would imply that we need at least a dependency on rcutils and/or rmw. Obviously, those are just type definitions, so it might be ok.
b) We tried to have a closer look and see how to write this to sqlite. It seems rcutils_char_array_t
consists of four parts, the name-sake char
array with the real content, two size bytes and another struct of allocator functions. I don't exactly understand the last part, the rcutils_allocator_t
. We can't write it to a database (at least I wouldn't know how) and I don't understand how we can easily generate this part - can we always take the default from the rmw? How does this work in relation to part a) of my trouble?
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.
a) The original design idea was to have librosbag2_storage independent of ros2. This part here would imply that we need at least a dependency on rcutils and/or rmw. Obviously, those are just type definitions, so it might be ok.
As much as possible it should be disconnected from ROS 2, to make it as portable as possible. If it's just rcutils
then it shouldn't be too hard to do that, as rcutils
doesn't really have any dependencies (no run dependencies, ament and python-empy as build depends). A dependency like rmw
or rosidl
, on the other hand, would be a red flag for me.
We tried to have a closer look and see how to write this to sqlite. It seems
rcutils_char_array_t
consists of four parts, the name-sake char array with the real content, two size bytes and another struct of allocator functions. I don't exactly understand the last part, thercutils_allocator_t
. We can't write it to a database (at least I wouldn't know how) and I don't understand how we can easily generate this part - can we always take the default from thermw
? How does this work in relation to part a) of my trouble?
You don't need to store the allocator structure in the database. That's just used to resize the serialized message and later to "delete it". Since the function that writes it to the database could essentially take a const rcutils_char_array_t *
, the allocator would not be used nor is it needed when bringing it out of the database later.
On the other side (reading a bag), some allocator will be used to create the space in the outgoing rcutils_char_array_t
instance so that the data from the database can be copied there.
There's no need for rmw
in any of these steps. The allocator type and all related functions are also part of rcutils
.
The other alternative would be to provide duplicate data types for the character array and allocator in this library itself. Converting between the version here and rcutils
should be straightforward, but it would be nice to reuse what's in rctuils
.
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.
Thanks, sounds good to me. We'll go with this then!
Copy of the last comment from the feature branch #18 (comment)
|
…ing with ReadWrite io_flag - This avoids the logging of a 'failed to read metadata' error when recording a new bag
-In this way it is not confused with the storage id (e.g. sqlite3)
…ing with ReadWrite io_flag - This avoids the logging of a 'failed to read metadata' error when recording a new bag
-In this way it is not confused with the storage id (e.g. sqlite3)
* Display bag summary using `ros2 bag info` * Improve process execution helper to handle the working directory * Use metadata filename in sqlite storage to determine database name * GH-109 Write metadata file on Windows by hand - On Windows, the process is killed hard and thus does not write its metadata file - Since this is an issue with the test setup that seems very hard to fix, for now we just write the metadata file on our own * Remove empty bag folder if record gets aborted and no files are created - For example is neither --all nor topics are specified or if a non exsisting storage plugin is specified * Fail gracefully if a runtime error occurs when trying to record or play - For example if the storage plugin specified by the user at record does not exist * Log error in case of failing when loading metadata, and minor refactoring * Add comment to version field * Allow rosbag2 info without yaml file Currently only supported on rosbag2 side: - Allow passing a storage identifier to rosbag2::Info() - If a yaml file exists, read info from yaml - If no yaml file exists and a storage identifier was passed open storage and read info directly * GH-7 Don't try to read database name from metadata file when opening with ReadWrite io_flag - This avoids the logging of a 'failed to read metadata' error when recording a new bag * GH-7 Rename 'storage format' into 'serialization format' -In this way it is not confused with the storage id (e.g. sqlite3) * GH-7 Improve failure conditions * GH-7 Cleanup of superfluous forward declarations * GH-7 Further improve exception handling
* Updates the play_next API to play N messages. - Makes tests pass with the previous functionality (just one message). - No change to the ROS interface. * Changes PlayNext.srv interface definition and modifies the player accordingly. * Adds tests for play_next(N). * Update rosbag2_transport/src/rosbag2_transport/player.cpp * Apply suggestions from code review Co-authored-by: Geoffrey Biggs <gbiggs@killbots.net> * Adds clarification string Co-authored-by: Geoffrey Biggs <gbiggs@killbots.net>
Connects to #8
This PR sets pluginlib in place to generically load storage plugins which implement this base class.