Hi again! Here is the second part of our visit to the magical world of contexts. Yesterday we brushed on a few simple uses for them, now let's dive into a slightly more sensitive subject.
Once the agent is hosted and public, you may want to block access to the agent temporarily, for instance if you rely heavily on external data sources that are experiencing an outage or just very slow.
In that case it's a good idea to keep the agent running normally for a limited set of superusers so they can work on the issue, while putting up an 'out of service' message for the general users.
//The message is by default empty. If it's not then the agent knows that we want it muted.
variable PUBLIC.OUTAGE_MESSAGE = ""
context AgentIsDisabled {out-of-context="0" in-context="1000" condition="!IsSuperUser() && PUBLIC.OUTAGE_MESSAGE ne \"\""}
start context AgentIsDisabled
+ =AnythingStrong
- PUBLIC.OUTAGE_MESSAGE
end context AgentIsDisabled
// SuperUsers only an change the agent status. Easy! We've already prepared for that.
start context RestrictedStrings
+ disable agent REASON=AnythingRaw
PUBLIC.OUTAGE_MESSAGE = REASON
+ enable agent
PUBLIC.OUTAGE_MESSAGE = ""
end context RestrictedStrings
Here the condition is simply to check if the outage message is empty or not, except for super users. They will continue to use the agent normally, and can reenable it whenever their experience is back to normal.
Now, there is one problem in this code... did you spot it?
Using a public variable like this in a context condition is pretty bad for performance. Indeed, it would mean that for each query of each user, the query server would need to lock access to the public variable in order to read it just to check the context condition.
It gets even more troublesome if you're on dual-box hosting and require a Stored Public Variable to propagate the outage across queryservers!
What is the solution then?
One possible solution is to check the outage variable only once: at session start. All you need for this is to keep a local session variable of that setting.
stored variable PUBLIC.STORED_OUTAGE_MESSAGE = ""
variable G_OUTAGE_MESSAGE = ""
procedure ABStartSessionProc()
// This procedure can exist independently in any buddyscript file, and is called at session start
if !IsSuperUser()
lock profile // locking is required for stored public variables.
G_OUTAGE_MESSAGE = PUBLIC.STORED_OUTAGE_MESSAGE
context AgentIsDisabled {out-of-context="0" in-context="1000" condition="G_OUTAGE_MESSAGE ne \"\""} //note that we don't need to check for SuperUsers here as as it's done at session start
start context AgentIsDisabled
+ =AnythingStrong
- G_OUTAGE_MESSAGE
end context AgentIsDisabled
start context RestrictedStrings
+ disable agent REASON=AnythingStrong
lock profile
PUBLIC.STORED_OUTAGE_MESSAGE = REASON
+ enable agent
lock profile
PUBLIC.STORED_OUTAGE_MESSAGE = ""
end context RestrictedStrings
As you may have noticed, there is a caveat to this solution: since we only check the message at session start, the flag takes some time to propagate as only new users will be getting the message.
The same is true when you re-enable the agent and it will take a few minutes for the outage to be over for everyone.
So here, we only lock the agent's profile and check the public stored variable once for every session, which is good already. But can we do better?
Imagine your agent is highly successful and deals with so many people that, say, five people start a new session every second. Don't we all dream of having an agent that popular! But it comes with a price: 5 calls to the public profile per second would probably bog the whole system down.
What do you do then? Well, add yet another layer of buffering of course!
Back to our friend the "basic" public variable. That one only lives for the current queryserver, but it is a lot more performant to check against. All we'll need is a background procedure to update the public variable every few minutes, so as to transmit the orders from the top to the base:
stored variable PUBLIC.STORED_OUTAGE_MESSAGE_FOR_ALL_USERS = ""
variable PUBLIC.OUTAGE_MESSAGE_FOR_ALL_USERS_OF_THIS_SERVER = ""
variable G_OUTAGE_MESSAGE_FOR_THIS_USER = ""
procedure Background_UpdateOutageMessage() startup every 1 minute
// This procedure is called at startup and every minute, independently of any user session.
lock profile
PUBLIC.OUTAGE_MESSAGE_FOR_ALL_USERS_OF_THIS_SERVER = PUBLIC.STORED_OUTAGE_MESSAGE_FOR_ALL_USERS
procedure ABStartSessionProc()
if !IsSuperUser()
G_OUTAGE_MESSAGE_FOR_THIS_USER = PUBLIC.OUTAGE_MESSAGE_FOR_ALL_USERS_OF_THIS_SERVER
context AgentIsDisabled {out-of-context="0" in-context="1000" condition="G_OUTAGE_MESSAGE_FOR_THIS_USER ne \"\""}
start context AgentIsDisabled
+ =AnythingStrong
- G_OUTAGE_MESSAGE_FOR_THIS_USER
end context AgentIsDisabled
start context RestrictedStrings
+ disable agent REASON=AnythingStrong
PUBLIC.STORED_OUTAGE_MESSAGE_FOR_ALL_USERS = REASON
+ enable agent
PUBLIC.STORED_OUTAGE_MESSAGE_FOR_ALL_USERS = ""
end context RestrictedStrings
Here you have a piece of code, slighly more complex, but that will sustain any kind of traffic without budging, with the only downside of taking up to one more minute to propagate the change of the agent's status.
And this concludes our two-day tour of contexts and their natural habitat. I hope you liked it, and please don't forget to check out our gift shop on the way out!