[Coldbox 7.2.1] Is It Possible to Access the RC Scope From a Coldbox Scheduled Task?

I am migrating my apps from old CFML Scheduler to the Coldbox Scheduler. However, I’ve run into an issue that I can’t seem to resolve or troubleshoot very easily because the task executions don’t show up in FusionReactor. My guess is because the Coldbox task executions aren’t typical requests.

My goal is to execute an event within a task via runEvent(), however the event requires a specific value in the rc scope.

When my task executes, I see in the logs that the task fails. I would have expected a validation exception, but instead I get a null pointer exception.

Here’s my Scheduler.cfc configuration:

task( "HourlyMaintenance" )
  .call( function() {

	rc.name = "HourlyMaintenance"; // <-- required value in RC scope (Does not work)
	
	runEvent( 
		event = "v1:maintenance.run",
		eventArguments = { 
			scheduler = true
		} 
	);
	
	// ... etc.

The above doesn’t work, because runEvent() doesn’t have access to the caller’s rc scope.

The v1:maintenance:run action fails validation via the following check at the beginning of the function:

function run( event, rc, prc, scheduler=false ) {
        
	validateOrFail( 
		target = rc,
		constraints = {
			"name": { 
				required: true,  // <-- validation fails here
				requiredMessage: "You must specify the name of the maintenance plan you want to run."
			}
		} 
	);
        
    // ... etc.

What’s strange is that instead of the expected validation exception, the Scheduler throws a null pointer exception in coldbox\system\RestHandler.cfc when it attempts to execute the setHTTPHeader() method (line 1805) for the x-current-route header. I have a feeling this might be a bug in the Coldbox scheduler, because I think it should be able to fail gracefully, just like a normal request.

// Name Exists
else if ( !isNull( arguments.name ) ) {
   getPageContext()
		.getResponse()
		.addHeader( javacast( "string", arguments.name ), javacast( "string", arguments.value ) );
	variables.responseHeaders[ arguments.name ] = arguments.value;

Aside from the above oddity, I am curious if there’s a way to access and populate the rc scope from within a Coldbox task()? The only workaround I can think of would be to use eventArguments, but I try to use those sparingly because they often require more conditional checks in my handlers.

I would love it if runEvent() had rcAppend and prcAppend arguments, similar to the Coldbox Router. Maybe this would be something to add to the Coldbox wish list. :wink:

If anyone has any other thoughts, workarounds, or ideas, please do share!

So there are a couple of things going on in your code examples.

task( "HourlyMaintenance" )
  .call( function() {

	rc.name = "HourlyMaintenance"; // <-- required value in RC scope (Does not work)
	
	runEvent( 
		event = "v1:maintenance.run",
		eventArguments = { 
			scheduler = true
		} 
	);

When you set rc.name there, you are creating a new struct with a new key. You are in a thread at that point, rather than a Coldbox request. In addition, you are not passing in the other arguments your function needs to run.

If you want to use an actual RequestContext objects ( with all of the methods ), then you would need to retrieve one from the controller:

var event = application.cbController.getRequestService().getContext();
event.setValue( "name", "HourlyMaintenance" );

Then your event args can be:

var eventArgs = {
   "event" : event, 
   "rc" : event.getCollection(),
   "prc" : event.getPrivateCollection(),
   "scheduler" : true
};

In which case you will have access to all of the methods of the event and validate it. Scheduled tasks and async ops can be tricky because, while the creation of the task makes the existing scopes available, it does not operate as an actual Coldbox request.

2 Likes

Thank you for the insights, @jclausen!

You’re absolutely right. I have been thinking about Coldbox tasks wrong. I shouldn’t think of them as a request at all and they shouldn’t need to execute an event (or route) at all.

Instead, I should have my tasks interact directly with models.

So for example, I could change my original task definition to:

task( "HourlyMaintenance" )
  .call( function() {
	getInstance( "HourlyMaintenanceRunner" ).run();

    // ... etc

I found your explanation of how to create and simulate a Coldbox event fascinating and I may play with that idea just for fun to see what is possible.

Yes, there’s really no need to run handler events, if you can just go direct. It’s OK to do so, of course, if you need to do something with the response or there’s logic in the handler you don’t want to duplicate.

If your tasks do run through events, though, you’ll to find some way to construct an acceptable RequestContext for those events.