Given: extension that has a properly configured cronjob (let's say, every 5 minutes) in Config.xml. Also, the system cron is set to run Magento's cron.sh. The cronjob has to run a couple of times after the extension installed, and when it has no more data to process then it becomes obsolete.
Problem: the job isn't needed after it had processed all the data. However, its setup in Config.xml causes it to run every 5 minutes forever, just to check that there is no more data and die.
Question: is there any proper way (maybe with the cron_schedule table...) to 'dismiss' the cronjob programmatically from its own PHP when it sees that there is no more data? Or any other way?
The cron is used since the extension installation process shouldn't be interrupted. Maybe it's possible to schedule some PHP code in some other way than cron (but within Magento)? Thought about threading but since there is no guarantee that this feature will be built in, this doesn't seem to be the option....
Thanks in advance!
So, I found 2 possible solutions: 1) it seems to be possible to create/remove crontabs via core_config_data table without config.xml; 2) remove the crontab node from config.xml after all data is processed + clean the cache + remove all pending tasks. I've managed to implement the 2nd, and it works (I know that the 1st approach is much better, but I just had no time to dig it out).
The 2nd looks like:
if ($more_data) {
// processing...
} else { // Dismissing the cron
$config_xml_path = Mage::getModuleDir('etc', 'the_extension') . '/config.xml';
$config_xml = simplexml_load_file($config_xml_path) or die("Error: Cannot create object");
if (isset($config_xml) && isset($config_xml->crontab)) {
unset($config_xml->crontab);
$config_xml->asXML($config_xml_path);
}
// Cleaning
Mage::app()->cleanCache();
$schedule = Mage::getModel('cron/schedule');
$sch_col = $schedule->getCollection()
->addFilter('job_code', 'the_extension_cronFunc')
->addFilter('status', 'pending');
foreach ($sch_col as $s) {
$s->delete();
}
}
Related
apparently there is something in my code that is stuck as on occasions it causes the script to run until time out at 6minutes.
I am still trying to find that code but in the meantime, i really need to prevent the waiting. My script typically needs 10 seconds only.
Is there a way for me to set such that any script that hits 10 seconds should be terminated.
much appreciated!
First of all, isn't it better to rewrite the code so that it does not reach 6 minutes?
Reference URL
If there is a possibility that it will exceed 6 minutes, try using a trigger to execute it for more than 6 minutes.
Reference URL
If there are multiple commands or is executed repeatedly, then TheMaster's recommendation in the comments is a good thing to be considered.
This will how it would look like.
function sampleFunction() {
var startTime = Date.now();
// do your thing, initialization, etc.
while (true) {
// do your thing, main process
// if 10 seconds is done
if ((Date.now() - startTime) >= 10000)
return;
}
}
Limitations:
If the command before the if condition checking the time takes 5 minutes, then you will be able to exit the script after that execution which will be 5 minutes later.
You can't stop a single command mid-execution. You need to wait for your if condition to be executed.
Things to note:
Exiting script through the method above works best when numerous lines of codes are being repeated in a loop and consistently checking the if condition so you can exit the script when the time comes.
You can only exit the script in between commands, not during those commands. So using this between commands that take too much time might stop the script later than expected.
It is still best to debug what is happening and why that "BUG" happens, or maybe you missed something that is causing the unexpected behavior.
Upon providing the exact code, the community will be able to help you with a more specific answer. Until then, speculations could only be provided.
I have an artisan command, in which I am cleaning up some data which has gone bad. Before I actually delete the data, I want to do a dry run and show some of the implications that deleting that data may present.
The essesnce of my command is:
public function handle()
{
...
$this->dryRun($modelsToDelete); // Prints info to user
if ($this->confirm('Are you sure you want to delete?') {
$modelsToDelete->each->forceDelete();
}
...
}
public function dryRun($modelsToDelete)
{
...
DB::connection($connection)->beginTransaction();
$before = $this->findAllOrphans($models);
$modelsToDelete->each(function ($record) use ($bar) {
$record->forceDelete();
});
$after = $this->findAllOrphans($models);
DB::connection($connection)->rollBack();
// Print info about diff
...
}
The problem is that when I do the dry run, and confirm to delete, the actual operation is not persisting in the database. If I comment out the dry run and do the command, the operation does persist. I have checked DB::transactionLevel() before and after the dry run and real operation, and everything seems correct.
I have also tried using DB::connection($connection)->pretend(...), but still the same issue. I also tried doing DB::purge($connection) and DB::reconnect($connection) after rolling back.
Does anyone have any thoughts as to what is going on?
(Using Laravel v6.20.14)
after digging the source code, I found out that laravel set property "exists" to false after you call delete on model instance and it will not perform delete query again. you can reference:
https://github.com/laravel/framework/blob/9edd46fc6dcd550e4fd5d081bea37b0a43162165/src/Illuminate/Database/Eloquent/Model.php#L1173
https://github.com/laravel/framework/blob/9edd46fc6dcd550e4fd5d081bea37b0a43162165/src/Illuminate/Database/Eloquent/Model.php#L1129
and to make model instance can be deleted after dryRun, you should pass a deep copy to dryRun, for example:
$this->dryRun(unserialize(serialize($modelsToDelete)));
note: don't use php clone because it's create a shallow copy
Turn on MySQL's "General log".
Run the experiment that is giving you trouble.
Turn off that log.
The problem may be obvious in the log; if not show us the log.
I was stuck in a situation that I have initialised a namesapce with
default-ttl to 30 days. There was about 5 million data with that (30-day calculated) ttl-value. Actually, my requirement is that ttl should be zero(0), but It(ttl-30d) was kept with unaware or un-recognise.
So, Now I want to update prev(old) 5 million data with new ttl-value (Zero).
I've checked/tried "set-disable-eviction true", but it is not working, it is removing data according to (old)ttl-value.
How do I overcome out this? (and I want to retrieve the removed data, How can I?).
Someone help me.
First, eviction and expiration are two different mechanisms. You can disable evictions in various ways, such as the set-disable-eviction config parameter you've used. You cannot disable the cleanup of expired records. There's a good knowledge base FAQ What are Expiration, Eviction and Stop-Writes?. Unfortunately, the expired records that have been cleaned up are gone if their void time is in the past. If those records were merely evicted (i.e. removed before their void time due to crossing the namespace high-water mark for memory or disk) you can cold restart your node, and those records with a future TTL will come back. They won't return if either they were durably deleted or if their TTL is in the past (such records gets skipped).
As for resetting TTLs, the easiest way would be to do this through a record UDF that is applied to all the records in your namespace using a scan.
The UDF for your situation would be very simple:
ttl.lua
function to_zero_ttl(rec)
local rec_ttl = record.ttl(rec)
if rec_ttl > 0 then
record.set_ttl(rec, -1)
aerospike:update(rec)
end
end
In AQL:
$ aql
Aerospike Query Client
Version 3.12.0
C Client Version 4.1.4
Copyright 2012-2017 Aerospike. All rights reserved.
aql> register module './ttl.lua'
OK, 1 module added.
aql> execute ttl.to_zero_ttl() on test.foo
Using a Python script would be easier if you have more complex logic, with filters etc.
zero_ttl_operation = [operations.touch(-1)]
query = client.query(namespace, set_name)
query.add_ops(zero_ttl_operation)
policy = {}
job = query.execute_background(policy)
print(f'executing job {job}')
while True:
response = client.job_info(job, aerospike.JOB_SCAN, policy={'timeout': 60000})
print(f'job status: {response}')
if response['status'] != aerospike.JOB_STATUS_INPROGRESS:
break
time.sleep(0.5)
Aerospike v6 and Python SDK v7.
I use Beantalkd and Yii2 framework.
To add in queue I use something like this:
Yii::$app->beanstalk
->putInTube('tube2', ['param' => 'val'], PheanstalkInterface::DEFAULT_PRIORITY, PheanstalkInterface::DEFAULT_DELAY);
But now I need to plain some task right at specified time, is it possible with Beantalkd, or I need something like Resque?
You can play some task at a sepcified time by calculating the delay, and sending that as a parameter to your above example.
On the other hand, it would be good to store time based lists for example in Redis, and have a cron that reads the expired ones every minute and loads the jobs to beanstalkd.
Question on agents: I specifically want to create a Periodic Task, but only want to run it once every day, say 1am, not every 30 minutes which is the default. In the OnInvoke, do I simply check for the hour, and run it only if current hour matches that desired hour.
But on the next OnInvoke call, it will try to run again in 30 minute, maybe when it's 1:31am.
So I guess I'd use a stored boolean in the app settings to mark as "already run for today" or similar, and then check against that value?
If you specifically want to run a custom action at 1 am, i'm not sure that a single boolean would be enough to make it work.
I guess that you plan to reset your boolean at 1:31 to prepare the execution of the next day, but what if your periodic task is also called at 1h51 (so called more than 2 times between 1am and 2am).
How could this happen? Well maybe this could happen if the device is reboot but i'm not quiet sure about it. In any case, storing the last execution datetime somewhere and comparing it to the current one can be a safer way to ensure that your action is only invoked once per day.
One question remains : Where to store your boolean or datetime (depending which one you'll pick)?
AppSetting does not seem to be a recommanded place according msdn :
Passing information between the foreground app and background agents
can be challenging because it is not possible to predict if the agent
and the app will run simultaneously. The following are recommended
patterns for this.
For Periodic and Resource-intensive Agents: Use LINQ 2 SQL or a file in isolated storage that is guarded with a Mutex. For
one-direction communication where the foreground app writes and the
agent only reads, we recommend using an isolated storage file with a
Mutex. We recommend that you do not use IsolatedStorageSettings to
communicate between processes because it is possible for the data to
become corrupt.
A simple file in isolated storage should get the job done.
If you're going by date (once per day) and it's valid that the task can run at 11pm on a day and 1am the next, then after the agent has run you could store the current date (forgetting about time). Then whenever the agent runs again in 30 minutes, check if the date the task last ran is the same as the current date.
protected override void OnInvoke(ScheduledTask task)
{
var lastRunDate = (DateTime)IsolatedStorageSettings.ApplicationSettings["LastRunDate"];
if(DateTime.Today.Subtract(lastRunDate).Days > 0)
{
// it's a greater date than when the task last ran
// DO STUFF!
// save the date - we only care about the date part
IsolatedStorageSettings.ApplicationSettings["LastRunDate"] = DateTime.Today;
IsolatedStorageSettings.ApplicationSettings.Save();
}
NotifyComplete();
}