Git Product home page Git Product logo

Comments (32)

ameenross avatar ameenross commented on July 24, 2024

I should note that I don't want to use a route for my cron definitions. I also figured loading the cron definitions on every bootstrap introduces unnecessary system load. So I can only think of 2 "proper" ways to add cron runs now: via a route or via an artisan command (both manually added). IMO both are undesirable, as they require more than just defining a cron job.

A better solution IMO would be that Cron defines its own artisan command (and optionally a route), which then collects all cron definitions, perhaps by triggering an event. When all event listeners have registered their cron definitions, the command can run Cron::run()

An simple example of code implementing a cron run using such a mechanism would be:

Event::listen('cron.collect', function() {
    // A cronjob which will run every minute.
    Cron::add('SomeCronJob', '* * * * *', function() {
        return SomeCronJobHandler::cron();
    });
});

from cron.

liebig avatar liebig commented on July 24, 2024

Hi ameenross,

thanks for using Cron. Maybe your event listener is a good idea. But where will you define your jobs if you don't use a route or a command? Then it will be somewhere in the bootstrap and this will lead to an unnecessary system load.

from cron.

ameenross avatar ameenross commented on July 24, 2024

Yeah, I mentioned that concern as well, but in this case the only thing that is defined is an event listener and I assume that it incurs a lower overhead. It also makes it much easier for package developers to define their cron runs, and additionally means that app developers don't need to worry about creating a route. All they have to do is put php artisan cron:run in their crontab.
And maybe the solution is not optimal, another approach could be for the artisan command to execute some code of each service provider (for example ServiceProvider::cronJob if it exists) which would amount to the same functionality, but I figured that is a bit of an archaic way of implementing a hook system.

from cron.

ameenross avatar ameenross commented on July 24, 2024

I just did a small benchmark using tinker to test my assumption. Here is the code and results:

/* Add 1000 event listeners in a loop and measure the time taken. */
$start = microtime(TRUE);

for ($i = 0; $i < 1000; $i++) {
    Event::listen('cron.collect', function() {
        Cron::add("SomeCronJob$i", '* * * * *', function() {
            return SomeCronJobHandler::cron();
        });
    });
}

$time = microtime(TRUE) - $start;
print 'Time taken: ' . $time . "\n"; /* ~0.013s */
/* Add 1000 cron jobs in a loop and measure the time taken. */
$start = microtime(TRUE);

for ($i = 0; $i < 1000; $i++) {
    Cron::add("SomeCronJob$i", '* * * * *', function() {
        return SomeCronJobHandler::cron();
    });
}

$time = microtime(TRUE) - $start;
print 'Time taken: ' . $time . "\n"; /* ~0.23s */

So it's nearly a factor 20 faster to add an event listener, and an overhead of about a millisecond for 100 cron jobs is negligable IMO.

from cron.

liebig avatar liebig commented on July 24, 2024

That looks good, thank you for the benchmark. Okay I will fire an event before calling the run method and I will add a simple command which will run Cron and one command which will list all registered jobs. Did I miss something?

from cron.

ameenross avatar ameenross commented on July 24, 2024

I think that's about it, so basically we have:

php artisan cron:list

  • Fires cron.collect event
  • Prints the list of registered jobs

php artisan cron:run

  • Fires cron.collect event
  • Runs Cron::run()

By the way, as Cron::cronJobs is a private property, I think a function like Cron::listJobs is needed to return all registered cron jobs.

from cron.

liebig avatar liebig commented on July 24, 2024

This is a great plan. I will work on this next week. Thank you very much.

from cron.

ameenross avatar ameenross commented on July 24, 2024

Great! Any progress so far? I'm willing to create a pull request implementing it, if you want.

from cron.

liebig avatar liebig commented on July 24, 2024

Yes, everything should be finished. I tested everything with Windows, Debian is still outstanding but you can start testing, too.

For example add

Event::listen('cron.collectJobs', function($rundate) {
    Cron::add('example1', '* * * * *', function() {
                    // Do some crazy things successfully every minute
                    return 'Wuhu';
                });

    Cron::add('example2', '* * * * *', function() {
        // Do some crazy things successfully every minute
    });

    Cron::add('new name for disabled job', '* * * * *', function() {
        // Do some crazy things successfully every minute
    }, false);
});

to your global.php.

You can run the commands with php artisan cron:run and php artisan cron:list. I have added some more Events like cron.beforeRun, cron.collectJobs and cron.afterRun.

I hope you like it :)

from cron.

ameenross avatar ameenross commented on July 24, 2024

Cool! I hadn't noticed. Did you push the last stuff though? Because I don't see the beforeRun and afterRun events being fired. Also not seeing php artisan cron:run fire cron.collectJobs. Will test soon anyway.

from cron.

liebig avatar liebig commented on July 24, 2024

Yes, everything is pushed. Have a look at the last commit 42dc9eb. The Debian tests are passed now.

from cron.

ameenross avatar ameenross commented on July 24, 2024

Ah right, I see you put the cron.collectJobs event trigger in Cron::run(). I think it would be better to put it in RunCommand::fire(). People using both the old and new method of running cron jobs might run into issues where their new cron jobs will also run when their legacy cron jobs are being run.

The cron.beforeRun and cron.afterRun events should stay in Cron::run(), as that is the right place for them and won't cause any compatibility breakage.

from cron.

liebig avatar liebig commented on July 24, 2024

I thought about this but I want to give all people (using route and commands) the possibility to use this event for registering jobs. For example, if you want to switch from command to route (maybe only temporary), you can let your job definitions unchanged but can use a route where you only call the run method. The jobs in the boot file will be loaded, too. I like your idea with this event listeners so I think that route users maybe will use this functionality, too? What do you think?

from cron.

ameenross avatar ameenross commented on July 24, 2024

Well I agree with your reasoning, but I'm concerned about backward compatibility. Lets say an app developer has a route setup which runs a few cronjobs with the old method. They then install a package that defines a cronjob with the new method. The app developer ran a composer update and configured his crontab. Now he's in a situation where the legacy cronjobs he wrote will also run the newly installed package's cronjob (unless he first updates his code to use the new system). That means duplicate cron runs. Might not sound like a serious issue, but I know of situations (payment systems, I'm looking at you guys) where that would result in serious problems.

As for people needing to use the new system, just document it in the Readme and people will start picking it up. They will just have to move their Cron::add() calls somewhere else and change their route handler to run artisan cron:run. Simple and effective!

from cron.

liebig avatar liebig commented on July 24, 2024

You win. I will move the \Event::fire('cron.collectJobs'); to the command.

from cron.

liebig avatar liebig commented on July 24, 2024

Okay, it's done. Tests passed on Windows and Debian. Now it is your turn ;)

from cron.

moleculezz avatar moleculezz commented on July 24, 2024

Hi, great feature. Just wanted to add, that you can always use semver, as a way to specify breaking changes.

from cron.

ameenross avatar ameenross commented on July 24, 2024

The way it's implemented now is not a breaking change though. Will test tomorrow but am confident it will work!

from cron.

moleculezz avatar moleculezz commented on July 24, 2024

I was referring to the previous implementation. If you want to introduce a
breaking change, you can/should use semver to specify a breaking change.
A project/package consuming the package should define in his composer.json
to only allow non-breaking versions to be updated for the project.
These slides explains it nicely.

from cron.

liebig avatar liebig commented on July 24, 2024

Thanks for your introduction, Moleculezz. I will take this into account in the future. But for now I have a problem. The new release has new features but does not break backwards compatibility. But the current release is v0.9.5. So what should I do? Make a v0.9.6, a v0.10.1 or a v1.0.0?

from cron.

moleculezz avatar moleculezz commented on July 24, 2024

Semver defines versioning as the following: MAJOR.MINOR.PATCH
From what I understand, a new features without breaking backwards compatibility is a minor version.
So v0.10.0, would be appropriate.

from cron.

liebig avatar liebig commented on July 24, 2024

Yes, I read the documentation and I agree with you. But I can't find a version in the document which has a number higher than 9. So I was confused.

from cron.

moleculezz avatar moleculezz commented on July 24, 2024

If you look at definition number 2 on semver, you will see an example.

from cron.

ameenross avatar ameenross commented on July 24, 2024

From the FAQ:

How should I deal with revisions in the 0.y.z initial development phase?

The simplest thing to do is start your initial development release at 0.1.0 and then increment the minor version for each subsequent release.

How do I know when to release 1.0.0?

If your software is being used in production, it should probably already be 1.0.0. If you have a stable API on which users have come to depend, you should be 1.0.0. If you're worrying a lot about backwards compatibility, you should probably already be 1.0.0.

So I think personally that it would be good to release 1.0.0 soon. Maybe after you provide a route that calls Artisan::call('cron:run') with some sort of auto-generated security token stored in a configuration file. That way it will be easy, but safe for app developers to setup a third party cron service. I'll open up a separate issue for that, because I think it's another good idea :)

from cron.

ameenross avatar ameenross commented on July 24, 2024

By the way, I tested it and it works. Just have 2 ideas for cosmetics, first is to add a line ending after the output of the commands, the other is to create an output similar to the php artisan routes command. There might be a function to produce that output, but Laravel's API docs feel like a maze in the dark so I haven't found it yet (if it exists).

from cron.

ameenross avatar ameenross commented on July 24, 2024

Just found it, it's Symfony: http://symfony.com/doc/current/components/console/helpers/tablehelper.html

from cron.

liebig avatar liebig commented on July 24, 2024

Table Helper is awesome! I will add it ;)

from cron.

ameenross avatar ameenross commented on July 24, 2024

Just created a pull request for it :)

from cron.

liebig avatar liebig commented on July 24, 2024

Yeah, the request was added. Thank you. I was too slow :D The pull request for the run command was merged, too.

from cron.

liebig avatar liebig commented on July 24, 2024

Can I close this? I think we implemented all requested features.

from cron.

ameenross avatar ameenross commented on July 24, 2024

yeah of course :) mission accomplished

from cron.

liebig avatar liebig commented on July 24, 2024

Great, thank you :)

from cron.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.