Skip to content

Commit

Permalink
Update plugin documentation steemit#1331
Browse files Browse the repository at this point in the history
  • Loading branch information
mvandeberg committed Sep 12, 2017
1 parent 2de89fc commit 0f55546
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 141 deletions.
42 changes: 30 additions & 12 deletions doc/plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,44 @@
How plugins work
----------------

All plugins in the `libraries/plugins` directory are iterated over by `CMakeLists.txt` and placed in a CMake environment variable `STEEM_INTERNAL_PLUGINS`, which is used to create a runtime-accessible list of available plugins used by the argument parsing.
All plugins in the `libraries/plugins` directory are iterated over by `CMakeLists.txt`. The manifest directory iterates through all plugins, adding them to the `steem_plugins` build target. Any other build target wanting to access all plugins
available at build time should link to this target.

Similarly, `external_plugins` is set aside for third-party plugins. Just drop plugin code into `external_plugins` directory, `make steemd`, and the new plugin will be available.

There is a plugin in `example_plugins` called `hello_api` which is a working example of adding a custom API call.
There is a plugin in `example_plugins` called `example_api_plugin` which is a working example of adding a custom API call.

Registering plugins
-------------------

- Plugins are enabled with the `enable-plugin` config file option.
- When specifying plugins, you should specify `witness` and `account_history` in addition to the new plugins.
- Some plugins may keep records in the database (currently only `account_history` does). If you change whether such a plugin is disabled/enabled, you should also replay the chain. Detecting this situation and automatically replaying when needed will be implemented in a future release.
- If you want to make API's available publicly, you must use the `public-api` option.
- When specifying public API's, you should specify `database_api` and `login_api` in addition to the new plugins.
- The `api-user` option allows for password protected access to an API.
- Plugins are enabled with the `plugins` config file option.
- By default, steemd runs the `chain`, `p2p`, and `webserver` plugins.
- Some plugins may keep records in the database (such as `account_history`). If you change whether such a plugin is disabled/enabled, you should also replay the chain. Detecting this situation and automatically replaying when needed will be implemented in a future release.
- To make an API visible, include the associated plugin in the `plugins` config file option. Only APIs explicitly made available through the config will be registered.

Autogenerating code
-------------------

A skeleton structure containing much of the boilerplate of creating a new plugin with an API can be autogenerated by running `programs/util/newplugin.py`.

- Register signal handlers in `plugin_startup()` (if needed)
- Add methods to `myplugin_api` class and reflect them in `FC_API` declaration
- Register signal handlers and custom evaluators in `plugin_initialize()` (if needed)
- Register APIs in `plugin_initialize()`
- API plugins should be separate from the stateful plugin they grant access to. This allows for the API to be disabled while keeping track of state for the plugin.

Startup/Shutdown of Plugins
---------------------------

- Plugins can specify dependencies with the `APPBASE_PLUGIN_REQUIRES` macro, which takes a bubble list of dependencies.
- There is undefined behavior if there are circular dependencies. Be careful how you design the data flow between plugins to eliminate circular dependencies.
- `plugin_initialze` and `plugin_startup` both guarantee that any dependencies are already initialized or started before the dependent plugin is called.
- `plugin_shutdown` is called in the reverse order from startup. Any dependencies will still be running when shutdown is called.
- You can get a reference to a plugin with `appbase::app().get_plugin< PLUIGN >()` or a pointer with `appbase::app().find_plugin< PLUGIN >()`. get_plugin will fail and find_plugin will return a nullptr if the plugin is not registered or has not been initialized. These methods guarantee you are accessing initialized date.
- Because of the inialization order, it is safe to call get/find_plugin on dependencies in initialization, startup, and shutdown.
- It is only safe to call get/find_plugin on an optional dependency in startup. An optional dependency is not explicitly specified. It is simply some optional behavior if a plugin is enabled. See `condenser_api` for an example of optional dependencies.

Asynchronous APIs
-----------------

-The json rpc plugin dispatches API requests asynchronously. This means your API call can be preempted or delayed and must be designed with parallelism in mind.
- To help, chainbase provides a simple read/write locking mechanism: `with_read_lock` and `with_write_lock`.
- This is a global lock over the entire database.
- The methods take a lambda that will be protected with the lock. For most API calls, `with_read_lock` will be sufficient.
- To ensure consistent block times, the read lock automatically expires after 1 second. Design your API call with this in mind. If the API call takes longer than 1 second, your results are undefined. It is best to create limitations on the API call to ensure it does not take longer than 1 second to execute (shorter than 250ms is preferable).
10 changes: 10 additions & 0 deletions example_plugins/example_api_plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
file(GLOB HEADERS "include/steem/plugins/example_api_plugin/*.hpp")

add_library( example_api_plugin
${HEADERS}
example_api_plugin.cpp
)

target_link_libraries( example_api_plugin appbase steem_chain fc )
target_include_directories( example_api_plugin
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" )
90 changes: 90 additions & 0 deletions example_plugins/example_api_plugin/example_api_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#pragma once
#include <appbase/application.hpp>

#include <steem/plugins/json_rpc/json_rpc_plugin.hpp>

#define STEEM_EXAMPLE_API_PLUGIN_NAME "example_api"

namespace steem { namespace example_api_plugin {

using namespace appbase;


// Define API method arg and return types
typedef json_rpc::void_type hello_world_args;

struct hello_world_return
{
string message;
};


struct echo_args
{
string call;
};

struct echo_return
{
string response;
}


// All plugins must inherit from appbase::plugin
class example_api_plugin : public appbase::plugin< example_api_plugin >
{
public:
example_api_plugin();
virtual ~example_api_plugin();

// This defines what plugins are required to run this plugin.
// These plugins will load before this one and shutdown after.
APPBASE_PLUGIN_REQUIRES( (plugins::json_rpc::json_rpc_plugin) );

// This static method is a required by the appbase::plugin template
static const std::string& name() { static std::string name = STEEM_EXAMPLE_API_PLUGIN_NAME; return name; }

// Specify any config options here
virtual void set_program_options( options_description&, options_description& ) override {}

// These implement startup and shutdown logic for the plugin.
// plugin_initialize and plugin_startup are called such that dependencies go first
// plugin_shutdown goes in reverse order such the dependencies are running when shutting down.
virtual void plugin_initialize( const variables_map& options ) override;
virtual void plugin_startup() override;
virtual void plugin_shutdown() override;

// These are the API methods defined for the plugin
// APIs take struct args and return structs
hello_world_return hello_world( const hello_world_args& args );
echo_return echo( const echo_args& args );
};

example_api_plugin::example_api_plugin() {}
example_api_plugin::~example_api_plugin() {}

void example_api_plugin::plugin_initialize( const variables_map& options )
{
// This registers the API with the json rpc plugin
JSON_RPC_REGISTER_API( name(), (hello_world)(echo) );
}

void example_api_plugin::plugin_startup() {}
void example_api_plugin::plugin_shutdown() {}

hello_world_return hello_world( const hello_world_args& args )
{
return hello_world_return{ "Hello World" };
}

echo_return echo( const echo_args& args )
{
return echo_return{ args.call };
}

} } // steem::example_api_plugin

// Args and return types need to be reflected. hello_world_args does not because it is a typedef of a reflected type
FC_REFLECT( steem::example_api_plugin::hello_world_return, (message) )
FC_REFLECT( steem::example_api_plugin::echo_args, (call) )
FC_REFLECT( steem::example_api_plugin::echo_return, (response) )
10 changes: 0 additions & 10 deletions example_plugins/hello_api/CMakeLists.txt

This file was deleted.

117 changes: 0 additions & 117 deletions example_plugins/hello_api/hello_api_plugin.cpp

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

#define STEEM_TEST_API_PLUGIN_NAME "test_api"

#define REGISTER_API( api_name, ... )

namespace steem { namespace plugins { namespace test_api {

using namespace appbase;
Expand Down

0 comments on commit 0f55546

Please sign in to comment.