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

initial version of plugin based storage api #7

Merged
merged 59 commits into from
Aug 21, 2018
Merged

Conversation

Karsten1987
Copy link
Collaborator

@Karsten1987 Karsten1987 commented Jul 27, 2018

Connects to #8

This PR sets pluginlib in place to generically load storage plugins which implement this base class.

Copy link
Member

@wjwwood wjwwood left a 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();
Copy link
Member

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.

Copy link
Collaborator Author

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?

Copy link
Member

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;
Copy link
Member

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
Copy link
Member

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;
Copy link
Member

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)?

Copy link
Collaborator Author

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.

Copy link
Member

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;
Copy link
Member

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.

Copy link
Contributor

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.

@Karsten1987
Copy link
Collaborator Author

Thanks @wjwwood for the feedback. I'll address the comments ASAP.

You're right! This is early stage and not quite ready for review.
I just opened the PR for visibility. And to see how the pluginlib can be used for loading storage plugins.

@Karsten1987
Copy link
Collaborator Author

I'm also not sure about the naming, why is it rosbag2_storage::StorageHandle and not rosbag2_storage::Handle or rosbag2_storage::RosbagStorageHandle?

Does that also apply then to rosbag2_storage::StorageFactory and rosbag2_storage::StorageInterface?

@wjwwood
Copy link
Member

wjwwood commented Jul 31, 2018

Yeah, I would personally choose rosbag2_storage::RosbagStorageFactory, though I might consider calling it rosbag2_storage::RosbagStorageInterfaceFactory, since it produces the interface you use to create "rosbag handles" (or just "rosbag"s?). In fact, I'm starting to question the purpose of the interface factory, though I don't have complete context.


struct SerializedBagMessage
{
rcutils_char_array_t serialized_data;
Copy link
Contributor

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?

Copy link
Member

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, 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?

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.

Copy link
Contributor

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!

@Karsten1987
Copy link
Collaborator Author

Copy of the last comment from the feature branch #18 (comment)

We iterated a bit on the storage design. Basically, our suggestion would be:

We don't actually need a write-only-storage for now, so delete
Use exceptions instead of return booleans (this just changes the interface slightly)
Have a method open_readonly for read-only storage and open for general storage (i.e. the general storage interface has to implement both). This is better than only one "open" method for everything and the pluginlib has no problem with it
Since our initial demo was pushed to the branch, I pushed our changes for this, too - i.e. the demo would remain fully functional.

The interface does not yet use the RosbagSerializedMessage. This would be next.

@Karsten1987 Karsten1987 merged commit 05d24c8 into master Aug 21, 2018
@Karsten1987 Karsten1987 deleted the storage_interface branch August 21, 2018 22:58
botteroa-si added a commit to bosch-io/rosbag2 that referenced this pull request Oct 24, 2018
…ing with ReadWrite io_flag

- This avoids the logging of a 'failed to read metadata' error when recording a new bag
botteroa-si added a commit to bosch-io/rosbag2 that referenced this pull request Oct 25, 2018
-In this way it is not confused with the storage id (e.g. sqlite3)
Martin-Idel-SI pushed a commit to bosch-io/rosbag2 that referenced this pull request Oct 29, 2018
…ing with ReadWrite io_flag

- This avoids the logging of a 'failed to read metadata' error when recording a new bag
Martin-Idel-SI pushed a commit to bosch-io/rosbag2 that referenced this pull request Oct 29, 2018
-In this way it is not confused with the storage id (e.g. sqlite3)
Martin-Idel-SI added a commit to bosch-io/rosbag2 that referenced this pull request Oct 30, 2018
Martin-Idel-SI added a commit to bosch-io/rosbag2 that referenced this pull request Oct 30, 2018
Martin-Idel-SI added a commit to bosch-io/rosbag2 that referenced this pull request Oct 30, 2018
Karsten1987 pushed a commit that referenced this pull request Oct 30, 2018
* 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
agalbachicar added a commit to agalbachicar/rosbag2 that referenced this pull request Dec 22, 2021
* 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>
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.

None yet

6 participants