Managing Pico Lifecycle
Overview
The parent of a pico is responsible for initiating the creation and deletion of a pico. The KRL programmer initiates these processes by having the parent receive certain child creation or child deletion events. The KRL programmer can then interact with these processes (such as executing code when they have finished) by selecting on events that occur within the child and parent during the process.
Creating a Pico
A pico can be created by having the parent pico receive a wrangler:child_creation or wrangler:new_child_creation event. Wrangler will pass any attributes added to this event through the event chain.
raise wrangler event "child_creation" attributes { "name":"my_new_child", // The name of our new child "co_id":meta:rid, // An attribute that will be passed through. "Correlation ID". We will use this to identify the child as the one we made. "testParams":{"hello":"world"}, // An attribute that will be passed through. }
Once this event is raised, Wrangler will behind the scenes create a pico and add it to its internal list of children. The wrangler:children() function will now return an array including this child.
We can then write a rule that selects on the wrangler:child_initialized event to execute KRL code when the child has been created. We can use the attributes passed through the event chain to validate that this was the child we created, before executing our own code.
rule onNewTestChild { select when wrangler child_initialized where event:attr("co_id") == meta:rid // On a child_initialized event where the co_id attribute matches this rid pre { testParams = event:attrs{"testParams"} // assign the testParams attribute to "testParams" } if testParams && testParams{"hello"} then // If testParams exists and it has a value for the "hello" key, fire the rule noop() fired { raise example event "this_ruleset_created_new_child_with_test_params" attributes {} } }
Deleting a Pico
A pico can be deleted by having the parent pico receive a wrangler:child_deletion or wrangler:delete_children event. Wrangler will pass any attributes added to this event through the event chain.
raise wrangler event "child_deletion" attributes { "name":"my_new_child", "co_id":meta:rid, "testParams":{"hello":"world"}, }
As soon as wrangler:child_deletion is raised, Wrangler will remove the pico from its internal list of children and add it to the set of children being deleted. wrangler:children() will no longer return this child.
Note that deleting a child also deletes that child's own children using the same process.
We can then write a rule that selects on the wrangler:child_deleted event to execute KRL code when the child has been deleted. We can use the attributes passed through the event chain to validate that this was the child we deleted, before executing our own code.
rule onTestChildDeleted { select when wrangler child_deleted where event:attr("co_id") == meta:rid // On a child_deleted event where the co_id attribute matches this rid pre { testParams = event:attrs{"testParams"} // assign the testParams attribute to "testParams" } if testParams && testParams{"hello"} then // If testParams exists and it has a value for the "hello" key, fire the rule noop() fired { raise example event "this_ruleset_deleted_child_with_test_params" attributes {} } }
Managing Code Startup on Pico Creation
As a developer, you might want to run KRL with the creation of a pico for several different reasons. You might be using the pico as an abstraction for a "job" and want it to start the job as soon as it is created. You might want to do initialization within the pico so that it is ready to receive data from other picos or external sources.
One of the main ways to start code on creation of a pico is by executing code on ruleset installation. Wrangler raises the wrangler:ruleset_installed
event whenever you install a ruleset on a pico using wrangler (it uses the wrangler:install_rulesets_requested event). One of the attributes that wrangler:child_creation takes is a "rids"/"rid" parameter. When creating a new pico, Wrangler will install the list of rulesets given in this attribute in the new pico as it is created. Wrangler will then raise wrangler:ruleset_installed
as normal, which you can select on and run code with.
Important Notes:
- All the valid rulesets given to "rids" will be guaranteed to be installed by the time
wrangler:ruleset_installed
is raised in a new pico - Any attributes given in the wrangler:child_creation event will be in the attributes for the wrangler:ruleset_installed event(s). This is a useful way to give parameters to a new child pico's rulesets.
- Listing initialization rules first in rulesets is good practice, as it is the first thing a ruleset will ever do.
Run Code In New Child Example
// Creates a new child with the "example" ruleset installed and passes an attribute named "testParam" rule newExampleChild { select when example new_child always { raise wrangler event "child_creation" attributes { "rids":"example", "testParam":"hello" } } } // In the newly created child this rule will run rule inNewChild { select when wrangler ruleset_installed where rids >< meta:rid pre { paramFromParent = event:attr("testParam").klog("Should be 'hello'") } always { ent:initial_state := {} // Set a desired initial state of entity variables } }
Create Channel On Installation Example
This example is taken from io.picolabs.subscription ruleset.
rule create_wellKnown_Rx{ select when wrangler ruleset_installed where rids >< meta:rid pre{ channel = wellKnown_Rx() } if(channel.isnull() || channel{"type"} != "Tx_Rx") then engine:newChannel(meta:picoId, "wellKnown_Rx", "Tx_Rx") fired{ raise Tx_Rx event "wellKnown_Rx_created" attributes event:attrs; } else{ raise Tx_Rx event "wellKnown_Rx_not_created" attributes event:attrs; //exists } }
Create well known Rx rule selects on ruleset_installed event where rids matches its own ruleset rid. Checks for a well known channel, and creates it if one does not exist. Then raises an event detailing the creation. This rule guarantees a well known channel exist, which is a dependency of subscriptions.
Managing Code Cleanup on Pico Deletion
When writing KRL for a pico, you do not always have control over who that pico's parent is, and because the parent is responsible for choosing when the pico is deleted, you do not always have full control over when that may happen. Therefore, as a KRL developer, you may want to attach cleanup code to the event of a pico deletion, such as sending data to another pico or to a remote server.
Wrangler provides responses to the wrangler:ruleset_needs_cleanup_period event and wrangler:cleanup_finished event to manage this process. If you want your ruleset to be guaranteed some time to clean up before a pico is deleted, you can raise wrangler:ruleset_needs_cleanup_period on the ruleset installation.
rule register_for_cleanup { select when wrangler ruleset_installed where event:attr("rids") >< meta:rid // On this ruleset being installed always { raise wrangler event "ruleset_needs_cleanup_period" attributes { // raise the event "domain":meta:rid // the event requires a domain, and meta:rid is a natural choice for this } } }
Once this event is raised the domain will be added to an internal list. Then when the parent decides to delete the child, Wrangler will raise a wrangler:rulesets_need_to_cleanup event. The KRL programmer reacts to this event to cleanup, then when done cleaning up, raise a wrangler:cleanup_finished event. It is good practice to raise the wrangler:cleanup_finished event when done so the pico can be cleaned up for space quickly. Otherwise Wrangler waits a timeout period and then simply deletes the pico anyway.
rule cleanup_subscriptions { select when wrangler rulesets_need_to_cleanup // When it is time to cleanup always { raise wrangler event "cancel_subscriptions" attributes event:attrs // Begin cleaning up } } /* . . . */ // This rule checks if we're done cleaning up after doing cleanup operations rule done_cleaning_up { select when wrangler subscription_removed or wrangler outbound_subscription_cancelled or wrangler inbound_subscription_cancelled or wrangler cancel_relationships // after wrangler rulesets_need_to_cleanup if wrangler:isMarkedForDeath() && not hasRelationships() then // wrangler:isMarkedForDeath() can be used to check if a pico is in the process of being deleted. noop() fired { raise wrangler event "cleanup_finished" attributes { // We raise wrangler:cleanup_finished to inform Wrangler we're done! "domain":meta:rid // We give the same domain that we used in wrangler:ruleset_needs_cleanup_period } } }
Implementation WIP
Right now ordering of cleanup is not guaranteed, so it is unwise to rely on mechanisms from other rulesets to be functioning during cleanup. For example, Wrangler tries to cancel all subscriptions as soon as wrangler:rulesets_need_to_cleanup is raised, so the programmer cannot rely on subscriptions. This will likely change in future Wrangler iterations.
Understanding the Lifecycle
This is a general overview of how the creation and deletion processes work under the hood, so that the KRL programmer can more fully understand what is going on behind the scenes.
Note that any attributes passed to the initial wrangler:child_deletion event will be passed as attributes to wrangler:rulesets_need_to_cleanup in all descendants, so this functions as a useful way of giving cleanup parameters to children.
Picos API
Returns an array of the children of the pico. Has optional parameter "name" which returns just the attributes for the children with the specific name if they exist, otherwise it returns attributes on every child if not provided. Has also optional parameter "allowRogue" which is by default true. Will not return child picos that are currently being deleted. An array containing a map for each returned pico. Each map contains: parent_eci: The ECI from the pico to the respective child. Should be used for parent-child interaction. name: The child pico's name id: The id of the child pico eci: The main Wrangler ECI for the child pico Returns a map giving basic meta information about the pico. This information includes the pico's ID, Wrangler ECI, and name. A map giving basic information about a pico with the following contents: id: The ID of the pico eci: The main Wrangler ECI of the pico name: The name of the pico Returns the name of the pico as recorded by Wrangler A string containing the name of the pico Returns the ID of the pico as recorded by Wrangler A string containing the ID of the pico Returns a string containing an ECI to the parent of the pico. If no parent exists then it will return the empty string. A string with the ECI of the parent pico. If no parent exists, then the string is the empty string. Returns a random English word that is unique among the children of the pico it is called on, if the pico has <= 200 children. Otherwise it will return a UUID. A string that is a common English word Returns the same data as wrangler:myself except it includes an ECI to the parent pico. A map containing meta information about the pico, including its Wrangler ECI, its Wrangler name, the pico ID, and an ECI for the parent pico. Returns true if this pico is in the process of being deleted. This function is used internally in Wrangler to lock tasks that cannot be interrupted by the pico being deleted, such as creating a new child pico. See Managing Pico Lifecycle for information on how to respond to notification of imminent pico deletion and allowing a ruleset to cleanup. True if the pico is in the process of being deleted False if the pico is not in the process of being deleted Returns the maximum amount of time given to a pico for its rulesets to cleanup before it is deleted. It is given in the format of the second parameter of the time:add() function. A map containing the set time allowed for ruleset cleanup before a pico is deleted. Given the pico ID of a child of Wrangler return the associated child map. Fast as it is a map access. Either A child pico map for the relevant child of this pico. NULL if the child does not exist Creates a new child for this pico with optional arguments for the child's name, and rulesets to install on it upon creation. It is important to note any additional attributes passed to it will also be accessible to the newly created child and its rulesets as event attributes, as long as their keys do not conflict with the keys used for the internal event chain. See Managing Pico Lifecycle for a more in-depth explanation. String Array String The RIDs of the rulesets to be installed on the new child pico. Each RID can be an entry in an array, or in a semicolon denoted list (e.g. "io.picolabs.ruleset_one;io.picolabs.ruleset_two"). String Array String Corresponding Raised Events: Deletes the direct child of this pico that has either the name or ID given as an event attribute. It will also delete that child's entire subtree of children. If both name and ID are given, they both will be evaluated. Deleting a child this way allows the child time for its rulesets to do any cleanup that they may have asked time for. It also cleans up parent-child channels, etc. String The ID of the direct child to be deleted. Corresponding Raised Events: Through engine calls forcibly removes children picos without necessary direct interaction with the children and without any opportunity for cleanup. Allows for deletion of misbehaving picos, picos that do not comply with Wrangler's deletion process, and other such issues. String The ID of the direct child to be deleted. Corresponding Raised Events: This event will both find children that Wrangler does not have recorded internally and delete children that Wrangler has recorded internally but do not exist anymore. Use this to detect any children added by rulesets other than Wrangler. Wrangler will ask the children first if they have Wrangler installed, if they do not then Wrangler will grab their admin channel through engine calls and add them to its children with a "rogue" attribute. Corresponding Raised Events: This is sent from the child to the parent once the child is done initializing. You can react to new children being created using this event. This event also guarantees that any rulesets passed to the create_child event have been installed. Note that any attributes passed to the wrangler:child_creation will also be present. String Array This event is raised within the pico when a child of this pico has been deleted through use of wrangler:child_deletion. id wrangler:child_creation_failure Raised when a pico is not able to be created. The most common cause of this is attempting to create a child pico with a duplicate name to another child pico. The same as the ones passed to wrangler:child_creation This event is raised within the pico when a child of this pico has been deleted through a force deletion using wrangler:force_child_deletion. id This event is raised as a result of a wrangler:child_sync event if any children that Wrangler did not have recorded are found. Their info is given in the attributes. nonexistent_children_removed wrangler:nonexistent_children_removed This event is raised as a result of a wrangler:child_sync event if any children that Wrangler had recorded did not actually exist. Functions
children
Parameters
Parameter Datatype Description name String Name of the pico(s) to search for allowRogue Boolean Determines whether the children call will return picos that do not have Wrangler installed on them. If this is set to false children() will only return picos that Wrangler is guaranteed to be installed on. It is false by default. Returns
children = wrangler:children()
/*
[
{
"parent_eci": "CyrmdG4PkiVYRUo2K69k2E",
"name": "Section CS462-1 Pico",
"id": "cjh6pbzun005zvde06bwy2pci",
"eci": "JfcEBxEK5WDKA69Bvach4Y"
},
{
"parent_eci": "Tg5ULPBYByJjsEoKpfsbQ2",
"name": "Section CS462-2 Pico",
"id": "cjh6pc28r0067vde04qd23t6o",
"eci": "56S414r4Uq4kGx6Xi1qmSs"
"rogue": true
}
]
*/
childrenWithWrangler = children(null, false)
/*
[
{
"parent_eci": "CyrmdG4PkiVYRUo2K69k2E",
"name": "Section CS462-1 Pico",
"id": "cjh6pbzun005zvde06bwy2pci",
"eci": "JfcEBxEK5WDKA69Bvach4Y"
}
]
*/
childrenNamedRefused = children("refused")
/*
[
{
"parent_eci":"5ehT3zigTKhHuHL571dCF5",
"name":"refused",
"id":"cjxf0yqiz000o0jbz4kbrgczt",
"eci":"TR4zpDwpKtuYAbsmBG7Wd7"
},
{
"parent_eci":"8sd1pvKqy54RmdY35bpRvM",
"name":"refused",
"id":"cjxf1dhp300150jbz3r5v3x2f",
"eci":"RGFe5aVgBTG4w2ix8fw52V"
}
]
*/
myself
Returns
myself_result = wrangler:myself()
/*
{
"id": "cjhtji2op009hpkrq35bcpodh",
"eci": "4bUS9W4HY5S1YfsAM9Ygw5",
"name": "This Pico"
}
*/
name
Returns
name = wrangler:name()
/*
"wovyn_sensor"
*/
id
Returns
id = wrangler:id()
/*
"cjxeypfo900070jbzes8736w2"
*/
parent_eci
Returns
channel_to_parent = wrangler:parent_eci()
/*
"HdLRkvF2ADhMfERDScr4ng"
*/
randomPicoName
Returns
random_name_result = wrangler:randomPicoName()
/*
"realize"
*/
getPicoMap
Returns
thisPico = wrangler:getPicoMap()
/*
{
"name":"product",
"id":"cjxeypfo900070jbzes8736w2",
"parent_eci":"T2cuE5zaGrQzDS8NbDVs4y",
"eci":"Vn2TWkKeJYcj26gXSbiWr3"
}
*/
isMarkedForDeath
Returns
picoUnsafe = wrangler:isMarkedForDeath()
/*
true
*/
timeForCleanup
Returns
timeToCleanup = wrangler:timeForCleanup()
/*
{ "minutes": 5 }
*/
getChild
Parameters
Parameter Datatype Description id String Child pico ID to access. Returns
childInfo = wrangler:getChild("cjxf0yqiz000o0jbz4kbrgczt")
/*
{
"parent_eci":"5ehT3zigTKhHuHL571dCF5",
"name":"refused",
"id":"cjxf0yqiz000o0jbz4kbrgczt",
"eci":"TR4zpDwpKtuYAbsmBG7Wd7"}
*/
childExists = wrangler:getChild("badID")
/*
null
*/
Received Events
create_child
wrangler:child_creation or wrangler:new_child_request
Attribute Datatype Description name String The name for the new child pico. Will generate a random name if not provided. rids rids_from_url The URLs rulesets to be installed on the new child pico. Each URL can be an entry in an array, or in a semicolon denoted list (e.g. "io.picolabs.ruleset_one;io.picolabs.ruleset_two"). The URL should return a file that can be parsed as KRL. delete_child
wrangler:child_deletion or wrangler:delete_children
Attribute Datatype Description name String The name of the children to be deleted. id delete_all Boolean When true all children will attempt to be deleted. force_child_deletion
wrangler:force_child_deletion or wrangler:force_children_deletion
Attribute Datatype Description name String The name of the children to be deleted. id delete_all Boolean When true all children will attempt to be deleted. child_sync
wrangler:child_sync
Raised Events
child_initialized
wrangler:child_initialized
Attributes
Attribute Datatype Description parent_eci String The ECI that the child pico has to communicate with this pico (the parent). name String The name of the new child pico id String The pico ID of the new child pico eci String An ECI for the parent to send events to the child pico. This is the same ECI in the map returned from the children() function. rids Array This attribute reflects rulesets successfully installed by giving the RIDs to wrangler:child_creation. This will not reflect rulesets installed by giving rulesets to install by URL to wrangler:child_creation. rids_to_install Any RIDs that were requested to be installed upon initialization of the child. This includes RIDs given in the rids parameter of the create_child event. This may include RIDs that were not valid RIDs and were not able to be installed. child_deleted
wrangler:child_deleted
Attributes
Attribute Datatype Description name String The name of the pico that was just deleted String The pico ID of the child that was just deleted child_creation_failure
Attributes
pico_forcibly_removed
wrangler:pico_forcibly_removed
Attributes
Attribute Datatype Description String The pico ID of the child that was just deleted ghost_children_added
wrangler:ghost_children_added
Attributes
Attribute Datatype Description found_children Map A map of the children that have been found. Attributes
Attribute Datatype Description removed_children Map A map of the children that have been removed.
Copyright Picolabs | Licensed under Creative Commons.