/** * Test method for testing the delete old database entries function * * @covers \Liebig\Cron\Cron::setDeleteDatabaseEntriesAfter * @covers \Liebig\Cron\Cron::run */ public function testDeleteOldDatabaseEntries() { $manager1 = new \Liebig\Cron\Models\Manager(); $date1 = new \DateTime(); date_sub($date1, date_interval_create_from_date_string('240 hours')); $manager1->rundate = $date1; $manager1->runtime = 0.01; $this->assertNotNull($manager1->save()); $newError1 = new \Liebig\Cron\Models\Job(); $newError1->name = "test1"; $newError1->return = "test1 fails"; $newError1->runtime = 0.001; $newError1->cron_manager_id = $manager1->id; $this->assertNotNull($newError1->save()); $newError2 = new \Liebig\Cron\Models\Job(); $newError2->name = "test2"; $newError2->return = "test2 fails"; $newError2->runtime = 0.002; $newError2->cron_manager_id = $manager1->id; $this->assertNotNull($newError2->save()); $manager2 = new \Liebig\Cron\Models\Manager(); $date2 = new \DateTime(); date_sub($date2, date_interval_create_from_date_string('240 hours')); $manager2->rundate = $date2; $manager2->runtime = 0.02; $this->assertNotNull($manager2->save()); $newError3 = new \Liebig\Cron\Models\Job(); $newError3->name = "test3"; $newError3->return = "tes31 fails"; $newError3->runtime = 0.003; $newError3->cron_manager_id = $manager2->id; $this->assertNotNull($newError3->save()); $manager3 = new \Liebig\Cron\Models\Manager(); $date3 = new \DateTime(); date_sub($date3, date_interval_create_from_date_string('10 hours')); $manager3->rundate = $date3; $manager3->runtime = 0.03; $this->assertNotNull($manager3->save()); $newError4 = new \Liebig\Cron\Models\Job(); $newError4->name = "test4"; $newError4->return = "test4 fails"; $newError4->runtime = 0.004; $newError4->cron_manager_id = $manager3->id; $this->assertNotNull($newError4->save()); $newError5 = new \Liebig\Cron\Models\Job(); $newError5->name = "test5"; $newError5->return = "test5 fails"; $newError5->runtime = 0.005; $newError5->cron_manager_id = $manager3->id; $this->assertNotNull($newError5->save()); $this->assertEquals(3, \Liebig\Cron\Models\Manager::count()); $this->assertEquals(5, \Liebig\Cron\Models\Job::count()); Cron::setDeleteDatabaseEntriesAfter(240); Cron::run(); $this->assertEquals(2, \Liebig\Cron\Models\Manager::count()); $this->assertEquals(2, \Liebig\Cron\Models\Job::count()); Cron::setDeleteDatabaseEntriesAfter(0); $manager4 = new \Liebig\Cron\Models\Manager(); $date4 = new \DateTime(); date_sub($date4, date_interval_create_from_date_string('2400 hours')); $manager4->rundate = $date4; $manager4->runtime = 0.04; $this->assertNotNull($manager4->save()); $newError6 = new \Liebig\Cron\Models\Job(); $newError6->name = "test6"; $newError6->return = "test6 fails"; $newError6->runtime = 0.006; $newError6->cron_manager_id = $manager4->id; $this->assertNotNull($newError6->save()); $newError7 = new \Liebig\Cron\Models\Job(); $newError7->name = "test7"; $newError7->return = "test7 fails"; $newError7->runtime = 0.007; $newError7->cron_manager_id = $manager3->id; $this->assertNotNull($newError7->save()); Cron::run(); $this->assertEquals(4, \Liebig\Cron\Models\Manager::count()); $this->assertEquals(4, \Liebig\Cron\Models\Job::count()); }
/** * Run the cron jobs * This method checks and runs all the defined cron jobs at the right time * This method (route) should be called automatically by a server or service * * @static * @param bool $checkRundateOnce optional When we check if a cronjob is due do we take into account the time when the run function was called ($checkRundateOnce = true) or do we take into account the time when each individual cronjob is executed ($checkRundateOnce = false) - default value is true * @return array Return an array with the rundate, runtime, errors and a result cron job array (with name, function return value, runtime in seconds) */ public static function run($checkRundateOnce = true) { // If a new lock file is created, $overlappingLockFile will be equals the file path $overlappingLockFile = ""; try { // Get the rundate $runDate = new \DateTime(); // Fire event before the Cron run will be executed \Event::fire('cron.beforeRun', array($runDate->getTimestamp())); // Check if prevent job overlapping is enabled and create lock file if true $preventOverlapping = self::getConfig('preventOverlapping', false); if (is_bool($preventOverlapping) && $preventOverlapping) { $storagePath = ""; // Fallback function for Laravel3 with helper function path('storage') if (function_exists('storage_path')) { $storagePath = storage_path(); } else { if (function_exists('path')) { $storagePath = path('storage'); } } if (!empty($storagePath)) { $lockFile = $storagePath . DIRECTORY_SEPARATOR . 'cron.lock'; if (file_exists($lockFile)) { self::log('warning', 'Lock file found - Cron is still running and prevent job overlapping is enabled - second Cron run will be terminated.'); if (self::isDatabaseLogging()) { // Create a new cronmanager database object with runtime -1 $cronmanager = new \Liebig\Cron\Models\Manager(); $cronmanager->rundate = $runDate; $cronmanager->runtime = -1; $cronmanager->save(); } // Fire the Cron locked event \Event::fire('cron.locked', array('lockfile' => $lockFile)); // Fire the after run event, because we are done here \Event::fire('cron.afterRun', array('rundate' => $runDate->getTimestamp(), 'inTime' => -1, 'runtime' => -1, 'errors' => 0, 'crons' => array(), 'lastRun' => array())); return array('rundate' => $runDate->getTimestamp(), 'inTime' => -1, 'runtime' => -1, 'errors' => 0, 'crons' => array(), 'lastRun' => array()); } else { // Create lock file touch($lockFile); if (!file_exists($lockFile)) { self::log('error', 'Could not create Cron lock file at ' . $lockFile . '.'); } else { // Lockfile created successfully // $overlappingLockFile is used to delete the lock file after Cron run $overlappingLockFile = $lockFile; } } } else { self::log('error', 'Could not get the path to the Laravel storage directory.'); } } // Get the run interval from Laravel config $runInterval = self::getRunInterval(); // Getting last run time only if database logging is enabled if (self::isDatabaseLogging()) { // Get the time (in seconds) between this and the last run and save this to $timeBetween $lastManager = \Liebig\Cron\Models\Manager::orderBy('rundate', 'DESC')->take(1)->get(); if (!empty($lastManager[0])) { $lastRun = new \DateTime($lastManager[0]->rundate); $timeBetween = $runDate->getTimestamp() - $lastRun->getTimestamp(); } else { // No previous cron job runs were found $timeBetween = -1; } } else { // If database logging is disabled // Cannot check if the cron run is in time $inTime = -1; } // Initialize the job and job error array and start the runtime calculation $allJobs = array(); $errorJobs = array(); $beforeAll = microtime(true); // Should we check if the cron expression is due once at method call if ($checkRundateOnce) { $checkTime = $runDate; } else { // or do we compare it to 'now' $checkTime = 'now'; } // For all defined cron jobs run this foreach (self::$cronJobs as $job) { // If the job is enabled and if the time for this job has come if ($job['enabled'] && $job['expression']->isDue($checkTime)) { // Get the start time of the job runtime $beforeOne = microtime(true); // Run the function and save the return to $return - all the magic goes here try { $return = $job['function'](); } catch (\Exception $e) { // If an uncaught exception occurs $return = get_class($e) . ' in job ' . $job['name'] . ': ' . $e->getMessage(); self::log('error', get_class($e) . ' in job ' . $job['name'] . ': ' . $e->getMessage() . "\r\n" . $e->getTraceAsString()); } // Get the end time of the job runtime $afterOne = microtime(true); // If the function returned not null then we assume that there was an error if (!is_null($return)) { // Add to error array array_push($errorJobs, array('name' => $job['name'], 'return' => $return, 'runtime' => $afterOne - $beforeOne)); // Log error job self::log('error', 'Job with the name ' . $job['name'] . ' was run with errors.'); // Fire event after executing a job with erros \Event::fire('cron.jobError', array('name' => $job['name'], 'return' => $return, 'runtime' => $afterOne - $beforeOne, 'rundate' => $runDate->getTimestamp())); } else { // Fire event after executing a job successfully \Event::fire('cron.jobSuccess', array('name' => $job['name'], 'runtime' => $afterOne - $beforeOne, 'rundate' => $runDate->getTimestamp())); } // Push the information of the ran cron job to the allJobs array (including name, return value, runtime) array_push($allJobs, array('name' => $job['name'], 'return' => $return, 'runtime' => $afterOne - $beforeOne)); } } // Get the end runtime after all cron job executions $afterAll = microtime(true); // If database logging is enabled, save manager und jobs to db if (self::isDatabaseLogging()) { // Create a new cronmanager database object for this run and save it $cronmanager = new \Liebig\Cron\Models\Manager(); $cronmanager->rundate = $runDate; $cronmanager->runtime = $afterAll - $beforeAll; $cronmanager->save(); // If the Cron run in time check is enabled, verify the time between the current and the last Cron run ($timeBetween) and compare it with the run interval if (self::isInTimeCheck()) { $inTime = false; // Check if the run between this and the last run is in time (30 seconds tolerance) and log this event if ($timeBetween === -1) { self::log('notice', 'Cron run with manager id ' . $cronmanager->id . ' has no previous managers.'); $inTime = -1; } elseif ($runInterval * 60 - $timeBetween < -30) { self::log('error', 'Cron run with manager id ' . $cronmanager->id . ' is with ' . $timeBetween . ' seconds between last run too late.'); $inTime = false; } elseif ($runInterval * 60 - $timeBetween > 30) { self::log('error', 'Cron run with manager id ' . $cronmanager->id . ' is with ' . $timeBetween . ' seconds between last run too fast.'); $inTime = false; } else { self::log('info', 'Cron run with manager id ' . $cronmanager->id . ' is with ' . $timeBetween . ' seconds between last run in time.'); $inTime = true; } } else { $inTime = -1; } if (self::isLogOnlyErrorJobsToDatabase()) { // Save error jobs only to database self::saveJobsFromArrayToDatabase($errorJobs, $cronmanager->id); } else { // Save all jobs to database self::saveJobsFromArrayToDatabase($allJobs, $cronmanager->id); } // Log the result of the cron run if (empty($errorJobs)) { self::log('info', 'The cron run with the manager id ' . $cronmanager->id . ' was finished without errors.'); } else { self::log('error', 'The cron run with the manager id ' . $cronmanager->id . ' was finished with ' . count($errorJobs) . ' errors.'); } // Check for old database entires and delete them self::deleteOldDatabaseEntries(); } else { // If database logging is disabled // Log the status of the cron job run without the cronmanager id if (empty($errorJobs)) { self::log('info', 'Cron run was finished without errors.'); } else { self::log('error', 'Cron run was finished with ' . count($errorJobs) . ' errors.'); } } // Removing overlapping lock file if lockfile was created if (!empty($overlappingLockFile)) { self::deleteLockFile($overlappingLockFile); } $returnArray = array('rundate' => $runDate->getTimestamp(), 'inTime' => $inTime, 'runtime' => $afterAll - $beforeAll, 'errors' => count($errorJobs), 'crons' => $allJobs); // If Cron was called before, add the latest call to the $returnArray if (isset($lastManager[0]) && !empty($lastManager[0])) { $returnArray['lastRun'] = array('rundate' => $lastManager[0]->rundate, 'runtime' => $lastManager[0]->runtime); } else { $returnArray['lastRun'] = array(); } // Fire event after the Cron run was executed \Event::fire('cron.afterRun', $returnArray); // Return the cron jobs array (including rundate, in-time boolean, runtime in seconds, number of errors and an array with the cron jobs reports) return $returnArray; } catch (\Exception $ex) { // Removing overlapping lock file if lockfile was created if (!empty($overlappingLockFile)) { self::deleteLockFile($overlappingLockFile); } throw $ex; } }