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.

Pico Creation Initiation
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. 

React To Pico Created
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.

Pico Deletion Initiation
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.

React To Pico Deleted
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

Creating a Child then Running Code Inside the New Child
// 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. 

Initialization Example
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.

Register for Cleanup
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.

Cleanup then Inform Wrangler
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.

Pico Creation


Pico Deletion

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


Functions


children

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.

Parameters

ParameterDatatypeDescription
nameStringName of the pico(s) to search for
allowRogueBooleanDetermines 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

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

Example
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 a map giving basic meta information about the pico. This information includes the pico's ID, Wrangler ECI, and name.

Returns

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

Example
myself_result = wrangler:myself()
/*
{
  "id": "cjhtji2op009hpkrq35bcpodh",
  "eci": "4bUS9W4HY5S1YfsAM9Ygw5",
  "name": "This Pico"
}
*/

name

Returns the name of the pico as recorded by Wrangler

Returns

A string containing the name of the pico

Example
name = wrangler:name()
/*
"wovyn_sensor"
*/

id

Returns the ID of the pico as recorded by Wrangler

Returns

A string containing the ID of the pico

Example
id = wrangler:id()
/*
"cjxeypfo900070jbzes8736w2"
*/

parent_eci

Returns a string containing an ECI to the parent of the pico. If no parent exists then it will return the empty string.

Returns

A string with the ECI of the parent pico. If no parent exists, then the string is the empty string.

Example
channel_to_parent = wrangler:parent_eci()
/*
"HdLRkvF2ADhMfERDScr4ng"
*/

randomPicoName

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.

Returns

A string that is a common English word

Example
random_name_result = wrangler:randomPicoName()
/*
"realize"
*/

getPicoMap

Returns the same data as wrangler:myself except it includes an ECI to the parent pico.

Returns

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.


Example
thisPico = wrangler:getPicoMap()
/*
{
"name":"product",
"id":"cjxeypfo900070jbzes8736w2",
"parent_eci":"T2cuE5zaGrQzDS8NbDVs4y",
"eci":"Vn2TWkKeJYcj26gXSbiWr3"
}
*/

isMarkedForDeath

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.

Returns

True if the pico is in the process of being deleted

False if the pico is not in the process of being deleted


Example
picoUnsafe = wrangler:isMarkedForDeath()
/*
true
*/

timeForCleanup

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.

Returns

A map containing the set time allowed for ruleset cleanup before a pico is deleted.


Example
timeToCleanup = wrangler:timeForCleanup()
/*
{ "minutes": 5 }
*/

getChild

 Given the pico ID of a child of Wrangler return the associated child map. Fast as it is a map access.

Parameters

ParameterDatatypeDescription
idStringChild pico ID to access.

Returns

Either

A child pico map for the relevant child of this pico.

NULL if the child does not exist


Example
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

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.

AttributeDatatypeDescription
nameStringThe name for the new child pico. Will generate a random name if not provided.
rids

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").

rids_from_url

String Array

String

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.

Corresponding Raised Events:


delete_child

wrangler:child_deletion or wrangler:delete_children

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.


AttributeDatatypeDescription
nameStringThe name of the children to be deleted.
id

String

The ID of the direct child to be deleted.

delete_allBooleanWhen true all children will attempt to be deleted. 

Corresponding Raised Events:


force_child_deletion

wrangler:force_child_deletion or wrangler:force_children_deletion

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. 

AttributeDatatypeDescription
nameStringThe name of the children to be deleted.
id

String

The ID of the direct child to be deleted.

delete_allBooleanWhen true all children will attempt to be deleted. 

Corresponding Raised Events:


child_sync

wrangler:child_sync

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:

Raised Events


child_initialized

wrangler:child_initialized

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.

Attributes

AttributeDatatypeDescription
parent_eciStringThe ECI that the child pico has to communicate with this pico (the parent).
nameStringThe name of the new child pico
idStringThe pico ID of the new child pico
eciStringAn ECI for the parent to send events to the child pico. This is the same ECI in the map returned from the children() function.
ridsArrayThis 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

String

Array

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

This event is raised within the pico when a child of this pico has been deleted through use of wrangler:child_deletion. 

Attributes

AttributeDatatypeDescription
nameStringThe name of the pico that was just deleted

id

StringThe pico ID of the child that was just deleted



child_creation_failure

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.

Attributes

The same as the ones passed to wrangler:child_creation


pico_forcibly_removed

wrangler:pico_forcibly_removed

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.

Attributes

AttributeDatatypeDescription

id

StringThe pico ID of the child that was just deleted

ghost_children_added

wrangler:ghost_children_added

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.

Attributes

AttributeDatatypeDescription
found_childrenMapA map of the children that have been found.


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. 

Attributes

AttributeDatatypeDescription
removed_childrenMapA map of the children that have been removed.

Copyright Picolabs | Licensed under Creative Commons.