Cronner – nástroj pro jednoduchou správu cronových úloh

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
stekycz
Člen | 152
+
0
-

Před pár dny jsem na Twitteru oznámil vytvoření tohoto nástroje a k mému překvapení získal poměrně rychle více jak 10 hvězd. Zakládám tedy toto vlákno proto, že by se mohl nástroj hodit i těm, kteří na Twitteru neobjevili informaci o jeho existenci :-)

Pro začátek upozorňuji, že se jedná o verzi 0.5.0. Verzi 1.0.0 uveřejním snad brzo. Rád bych nejprve získal nějaký feedback, názory a přípomínky od dalších lidí. Prosím, pište je pokud možno rovnou na GitHub.

Proč nástroj pro cron v PHP?
Protože ne každý web má své vlastní VPS na kterém si může nastavit kolik chce úloh. A také proto, že ne kvůli každé změně chci měnit nastavení přímo v cronu (resp. administraci hostingu). Tohle nastavení chci provést jednou a pokud možno na něj už nesahat.

A co to tedy umí?
Zatím toho moc není, ale to důležité tam je.

  • Nastavení periody spouštění úkolu.
  • Nastavení dní v týdnu, ve které má být úloha spuštěna.
  • Nastavení časů, během kterých může být úloha spuštěna.

EDIT: aktuální popis v Addons

Jak to použít?
Nástroj využívá komentářových anotací v PHP. Moje snaha je, aby bylo možné nástroj nasadit na existující projekt s minimálním úsilím. Jsou tam zatím nějaké mezery, ale do verze 1.0.0 by měly být doladěné.

Příklad použití by mohl vypadat takto:
Chci z nějaké webové služby automaticky stahovat data. Vím, že přes víkend je nemá smysl stahovat, protože je nikdo nevytváří a ani nikdo nepoužívá. Zároveň vím, že všechna nová data se veřejně objevují v dávkách 3× denně v různých časech. Jak to udělám cronnem? Já osobně nevím, takže pokud někdo víte, v komentáři klidně napište :-)

Třída s úlohou:

class CronTasks extends \stekycz\Cronner\Tasks {
    /**
     * @cronner-task Stahuje důležitá data z webové služby
     * @cronner-period 1 hour
     * @cronner-days working days
     * @cronner-time 08:00 - 09:00, 13:00-14:00, 20:00 - 21:00
     */
    public function downloadImportantData() {
        // Váš vlastní kód pro stažení a uložení dat
    }
}

Dále musíme mít Presenter, ve kterém budeme úlohy spouštět. Nastavíme tak jedinou úlohu přímo pro cron, kterou budeme spouštět jak chceme.

Presenter:

class CronPresenter extends \Nette\Application\UI\Presenter {
    private $cronner;

    public function injectCronner(Cronner $cronner) {
        $this->cronner = $cronner;
    }

    public function actionCron() {
        $this->cronner->addTasksCallback(function () {
            // Some magic code can be here :-)
            return new CronTasks();
        });
        $this->cronner->run();
        $this->terminate();
    }
}

A nesmíme zapomenou na konfiguraci v config.neon :-)

services:
    cronner: stekycz\Cronner\Cronner(new \stekycz\Cronner\TimestampStorage\FileStorage(%wwwDir%/../temp/cronner))

Podrobněji k použití v repozitáři na GitHubu.

Pokud máte jakékoli komentáře nebo náměty, šup s nima na GitHub :-) Stahovat můžeme buď přímo z repozitáře nebo pomocí composeru z packagist.org.

Editoval stekycz (17. 8. 2014 16:14)

castamir
Člen | 629
+
0
-

Vypadá to zajímavě, ale mám pár hloupých dotazů:

  • Jak ověřím, že to jede?
  • Jak to řeší situaci, kdy to spustím třeba 2×?
  • Jak to vypnu?
Felix
Nette Core | 1186
+
0
-

Vypada to fakt hezky. Spis by me zajimalo jak to poustet? Ono to reaguje na kazdy request? Nebo stejne se nevyhnu nastaveni crona v administraci hostingu na moji danou url. Tzn, pokud chci stahovat data kazdych 5min a hosting mi povoluje crona 1 za hodinu. Jak na to?

stekycz
Člen | 152
+
0
-

@castamir

  • Teď ještě není ověření možné, ale v issues už mám úkol, který to bude řešit.
  • Tuhle situaci to teď neřeší. Dávám si do issues. Díky!
  • Co myslíš vypnutím? Dočasně nebo trvale? Dočasné vypbutí by mělo vyřešit stejné issue jako pro bod 1. Trvale to vypneš odebráním anotace @cronner-task od metody.

@Felix
Záleží čistě na tobě jako vývojáři, kdy to budeš spouštět :-) Můžeš si vytvořit speciální akci pro crona, kterou nastavíš v nastavení hostingu jako cron akci (a tedy se jednomu nastavení v administraci hostingu nevyhneš). Další úlohy už ale stačí přidávat v kódu. Nebo si můžeš úlohy spouštět v jakékoli jiné akci, kterou uznáš za vhodnou, klidně při každém requestu ;-)

Bohužel nejsem kouzelník a pokud ti hosting nedovoluje pouštět úlohu častěji než 1× za hodinu, tak se ani častěji nespustí. Pokud má někdo nápad, jak to řešit jinak, sem s ním!

Má to ale jednu výhodu. Pokud se projekt rozroste a zvýšíš si balíček hostingu tak, že crona můžeš pouštět každou minutu, stačí změnit periodu pro crona jako celek a Cronner se postará o správné spouštění.

PS: Pokud ti hosting nedovolí crona častěji než hodinu a ty bys tu úlohu častěji pouštět chtěl, otázka je, zda a) máš správný hostingový balíček nebo b) opravdu potřebuješ tak často pouštět tu úlohu? ;-)

Honza Kuchař
Člen | 1662
+
+1
-

Viděl jsem moc zajímavé řešení u PhpBB. Ti to řeší takto…

Mějme šablonu, která se pošle uživateli (téměř) při každém requestu. V té šabloně bude mimo stránky ještě navíc něco typu <img src="blahblah/cronner" alt="..." />. A teď otázka – jak zajistit, aby se uživateli netočilo kolečko, že se stránka načítá, pokud zrovna běží nějaká úloha? PHP neumí ukončit spojení před dokončením skriptu. Však je tu jeden trik. Spojení umí ukončit prohlížeč, pokud ví, že už dostal všechna data, která potřeboval. Tedy PhpBB to dělá následovně:

ignore_user_abort(true);

// Output transparent gif
header('Cache-Control: no-cache');
header('Content-type: image/gif');
header('Content-length: 43'); // This cause browser to close connection after 43 bytes
echo base64_decode('R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==');

// Flush here to prevent browser from showing the page as loading while running cron.
flush();
Caine
Člen | 216
+
0
-

stekycz napsal(a):
Bohužel nejsem kouzelník a pokud ti hosting nedovoluje pouštět úlohu častěji než 1× za hodinu, tak se ani častěji nespustí. Pokud má někdo nápad, jak to řešit jinak, sem s ním!

Mno a co treba udelat script, co spousti sam sebe?

...
... provedeni uloh ...
...

sleep($delay);
if (is_file('php-cron.enabled')) {
	file_get_contents('http://na.sama.sebe'); //nebo pres curl, ci exec(), atp...
}

Samozrejme by se objevilo spoustu uskali napr rozlisovani, kdy to spustil cron a kdy se to spustilo samo, nebo jak se vyporadat s delkou vykonavani narocnejsich uloh, atp. ale i tak si myslim, ze by to bylo proveditelny…

stekycz
Člen | 152
+
0
-

Díky za Honzův a Cainův komentář! Oba přístupy jsou zajímavé a mají svá úskalí. Postupně se tedy vyjádřím k oběma.

Odkazovat na skript/akci, která pouští cron je celkem snadná a ani není potřeba, aby hosting cron podporoval. Nicméně do jisté míry odhalujeme ve vygenerovaném HTML fungování serveru. Dále by mohlo někomu vadit, že se načte stránka, ale až po jejím načtení se cronem upraví data, která stránka zobrazuje.

Spouštět speciální script stále dokola v řetězu se mi zdá možná o něco lepší, protože v zásadě neodkrývá nic. Pokud první spuštění provedeme na jednorázovou akci/skript, který pak ze serveru smažeme, nemusíme mít na hostingu vůbec cron. Nicméně tohle vyžaduje znalost maximální doby běhu skriptu a před jejím uplynutím spustit skript znovu. Navíc se může stát, že budeme neustále čekat na akci, která se spouští jednou za týden. Vytížení serveru sice zásadně nevzroste, ale při zvýšení návštěvnosti se to projevit může. Přeci jen, spouštět stále skript, který vlastně jen čeká, je zbytečné.

V každém případě se více přikláním ke druhé variantě. Záleží však na každém projektu, jak si to sám zařídí. V rámci Cronneru je možné vytvořit jen jakési doporučení.

Caine
Člen | 216
+
0
-

Sleep se do max doby behu scriptu nezapocitava (na linuxech).
Na serveru, kde je alespon nejakej cron, bych to urcite neretezil do nekonecna, ale jen v ramci intervalu toho crona (mam k dispozici 10 min, tak se bude retezit maximalne 9×, za predpokladu, ze budu chtit jednou za minutu overit, jestli nemam neco spustit)… A kdyz uz bych to cely takhle delal, udelal bych to asi tak, aby ten retezici script jen kontroloval, jestli se nema neco spustit, ale sam to uz nespoustel, a byl uplne co mozna nejjednodusi (klidne i cisty php), ktery pokud ho volas i treba kazdou minutu nebude mit naprosto zadnej vliv na vykon celyho serveru, protoze sam o sobe bezi < 10 ms..

stekycz
Člen | 152
+
0
-

Souhlasím, že v případě nějakého crona na serveru má smysl řetězit minimálně. Nicméně by mě zajímalo, kolik serverů s cronem neumožňuje navýšit dobu běhu scriptu na dobu periody mezi jednotlivýma úlohama. Spíše bych si teda zvýšil dobu běhu scriptu a používal sleep.

Co se týče skriptu pro kontrolu spuštění úloh, ten by v současnosti nemohl být úplně jednoduchý. Kontrola spuštění nějaké úlohy totiž probíhá až za běhu. Bylo by potřeba někam ukládat informaci o tom, kdy nejdříve má smysl nějakou úlohu spustit. Pak by to bylo bez problémů.

Souvisí s tím však další věc. Co když mám úlohu s týdenní periodou a během týdne přidám úlohu s periodou jedné hodiny? Musím smazat zapamatovaný čas dalšího spuštění crona a při prvním spuštění se znova vygeneruje „mapa“ spouštění úloh?

Když tak nad tím přemýšlím, mohl by v Cronneru existovat systém, který by si udržoval mapu úloha => další spuštění. Pokud by mapa neexistovala, prostě by se nově vytvořila. Pak by samotný Cronner umožňoval dotaz na nejbližší čas spuštění nějaké úlohy. Pak by bylo možné relativně rychle ověřovat nutnost spouštění Cronnera. Co si o tom myslíte?

Editoval stekycz (3. 3. 2013 16:15)

Caine
Člen | 216
+
0
-

Myslel jsem to jinak, to retezeni by se chovalo jen jako cron, tj periodicky by si to kontrolovalo seznam uloh ke spusteni a ty pripadne spoustel (otazka je, jak je spoustet paralelne ze scriptu). Uloha sama by si kontrolovala, jestli ma vyznam spoustet dalsi svy akce – tedy jako u normalniho crona.

xxxmisko
Člen | 140
+
0
-

Ahojte chlapci!

Chcem sa opýtať, vyvinul sa cronner, čo sa týka castamir-ových požiadaviek v druhom príspevku? Ďakujem

stekycz
Člen | 152
+
0
-

@xxxmisko Cronner zatím nepovažuji za finální/stabilní verzi (viz číslo verze). Tyto úkoly mám v issues na GitHubu a budu je řešit, za předpokladu, že je nevyřeší někdo jiný nějakým Pull Requestem :-)

xxxmisko
Člen | 140
+
0
-

už aby to bolo :D

Skippous
Člen | 21
+
0
-

Ahoj, zaprvé chci pochválit. Na první pohled to vypadá pěkně. Řeší to přesně logiku jak bych chtěl s cronem pracovat. Můžu se zeptat, jak to vypadá s dalším vývojem?

stekycz
Člen | 152
+
0
-

Další větší vývoj proběhne teď přes prázdniny a rád bych na konci léta vydal první stabilní verzi 1.0.0 :-)

duskohu
Člen | 778
+
0
-

Ahoj, Mam jeden taky navrh :-)
Dnes som narazil na problem ked mam niekolko Taskov v rade vsetky maju povolene sa spustat v tom istom case a vsetky dost dlho bezia. Bud zbehnu za 10 sec, to znamena ze uz nemaju co riesit, alebo bezia 40–50 sec. co znamena ze stale nieco riesia.
Na vela servroch je set_time_limit zakazane, takze toto sa neda pouzit.
To spozôbi ze 2 task mi padne. Neda sa nejako sledovat ako dlho bezal task a v pripade ze bezal nad limit (parameter) tak dalsi nespusti?

MartinitCZ
Člen | 580
+
0
-

@**duskohu**: Spíš než nesputit by bylo lepší jeho spuštění šoupnou. Sputit CRON B až po dokončení CRON A. Ale to už by bylo až moc složité, jelikož by se muselo monitorovat a někde „střádat“, co se aktuálně děje, resp. co Cronner dělá …

duskohu
Člen | 778
+
0
-

Skusal som nieco taketo, lenze toto asi nepojde kedze cez callback neviem ovplivnit foreach Cronnera.

class CronPresenter extends Presenter
{
	.......

	/** @var string */
	private $startTime;


	/**
	 * ACTION - Default
	 */
	public function actionDefault()
	{
		$this->cronner->onTaskBegin[] = function ($cronner, $task) {
			if (!$this->startTime) {
				$this->startTime = microtime(TRUE);
			} else {
				$end = microtime(TRUE);
				$time = $end - $this->startTime;
				if ($time > 3) { // Pocet sekund cez ktore nema presiahnut 1 process
					// AKO UROBIM BREAK FOREACH?
				}
			}
		};

		$this->cronner->addTasks($this->cron1);
		$this->cronner->addTasks($this->cron2);

		$this->cronner->run();
		$this->terminate();
	}
}
stekycz
Člen | 152
+
0
-

Něco podobného by určitě nebyl problém přidat. Otázka spíš je, zda by to opravdu přineslo požadovaný výsledek, tedy snížení množství errorů nebo jejich úplnou eliminaci.

Tvůj návrh fungovat bude, ale pokud samotný úkol potrvá déle, než kolik je maximální doba běhu určená serverem, chybě se nevyhneš. Stejně tak se chybě nevyhneš, když budeš mít (pro uvedený příklad) víc jak 20 tasků a každý potrvá 2.9 sekundy s nastaveným max_time na 1 minutu.

O něco více se mi líbí co nastínil @martinit. Tedy připravit interface/logiku, která bude postupně střádat délku běhu jednotlivých úkolů a bude počítat průměr doby běhy a na základě toho bude plánovat, které úkoly v daném běhu pustí a které ne. Využít by k tomu bylo možné již existujícího interface ITimestampStorage. Samozřejmě by bylo potřeba i nastavit maximální dobu běhu (kterou je možné nastavit už teď, jen by byla využita i jinak).

V tuhle chvíli si může udělat následující:

// Důležité je dědit od RuntimeException, které Cronner nepřeskakuje.
class CronnerInterruptedException extends \stekycz\Cronner\RuntimeException {}

class CronPresenter extends Presenter
{
    /** @var string */
    private $startTime;


    /**
     * ACTION - Default
     */
    public function actionDefault()
    {
        $this->cronner->onTaskBegin[] = function ($cronner, $task) {
            if (!$this->startTime) {
                $this->startTime = microtime(TRUE);
            } else {
                $end = microtime(TRUE);
                $time = $end - $this->startTime;
                if ($time > 3) { // Pocet sekund cez ktore nema presiahnut 1 process
                    // Zavolá se callback $cronner->onTaskError;
                    throw new CronnerInterruptedException();
                }
            }
        };

        $this->cronner->addTasks($this->cron1);
        $this->cronner->addTasks($this->cron2);

        try {
            $this->cronner->run();
        } catch (CronnerInterruptedException $e) {
            // Tady už víš, že Cronner foreach skončil
        }
        $this->terminate();
    }
}

Updated: 2013–11–12 1:21

Editoval stekycz (12. 11. 2013 1:21)

duskohu
Člen | 778
+
0
-

Toto funguje celkom pekne, dakujem, len drobnost:

try {
	$this->cronner->run();
} catch (CronnerInterruptedException $e) {
	// Toto neodchyti ziadnu vynimku, aj v pripade ze dam chytat \Exception
}
stekycz
Člen | 152
+
0
-

Jasně, díky za upozornění, upravil jsem původní příspěvek tak, aby seděl. Jednalo se o jinou rodičovskou výjimku.

duskohu
Člen | 778
+
0
-

@stekycz cau, uspesne pouzivam cronner. V posleddnej dobe som narazil na jeden problem. Mam definovene pravidlo:

/**
* @cronner-task Synchronize store
* @cronner-period 1 day
* @cronner-time 07:00 - 23:55
*/

Problem je v tom ze sa mi kazdy den spusta o hodinu neskor a neskor a nie kazdy den po 07:00, ale stale o hodinu neskor ako den pred. nevies mi poradit ako to riesit? Dik.

20.7 14:01
21.7 15:01
22.7 16:01
23.7 17:00

Edit: pouzivam Nette 2.1

Editoval duskohu (24. 7. 2014 10:21)

stekycz
Člen | 152
+
0
-

@duskohu Osobně si myslím, že problém bude někde v kontrole na spuštění úkolu. Možná bude problém v času, který se nastaví jako poslední spuštění. Nicméně předpokládám, se ti cronner zpracuje každou 1 hodinu, že?

duskohu
Člen | 778
+
0
-

@stekycz Neviem ci uplne rozumiem, Cron sa mi spusta kazdu 1 hodinu, lebo mam este aj dalsie tasky. Oki skusim si dumpnut tuto kontrolu a dam vediet. Dik.

duskohu
Člen | 778
+
0
-

@stekycz cau, takze $lastRunTime mi vracia datum posledneho spustenia tasku, tento datum sedi, takze v tom to nebude.

stekycz
Člen | 152
+
0
-

@duskohu Tomu celkem rozumím. Není ten $lastRunTime asi o vteřinu posunutý? Jaký dostaneš čas, když se k tomu připočte ta perioda? Když si ty časy dumpneš, dostaneš opravdu stejný čas nebo posunutý o vteřinu ve prospěch posunutého posledního spuštění?

stekycz
Člen | 152
+
0
-

@duskohu On možná problém bude tady. Pokud úkol nějakou chvíli trvá, tak se uloží pozdější čas a tím padem se spustí vždy až další nejbližší apuštění. Asi bych měl ukládat čas, který se nastartuje daný úkol nebo celý Cronner.

duskohu
Člen | 778
+
0
-

Cau, takze som si dal nejaky debug $now, $lastRunTime a saveRunTime a vymazal som cron cache nechal som to bezat nanovo. Zadanie pre cron bolo toto, pricom cron bezi kazdu hodinu.

	/**
	 * @cronner-period 1 day
	 * @cronner-time 07:00 - 23:55
	 */

vystup bol takyto:
[2014–08–02 07–01–06] $now:2014–08–02 07:01:06; $lastRunTime:null
[2014–08–02 07–01–10] saveRunTime:2014–08–02 07:01:10
[2014–08–02 08–01–10] $now:2014–08–02 08:01:10; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 09–00–54] $now:2014–08–02 09:00:54; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 10–01–07] $now:2014–08–02 10:01:07; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 11–00–51] $now:2014–08–02 11:00:51; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 12–01–42] $now:2014–08–02 12:01:42; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 13–00–54] $now:2014–08–02 13:00:53; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 14–01–10] $now:2014–08–02 14:01:10; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 15–00–59] $now:2014–08–02 15:00:59; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 16–01–16] $now:2014–08–02 16:01:16; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 17–00–46] $now:2014–08–02 17:00:46; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 18–01–15] $now:2014–08–02 18:01:15; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 19–00–57] $now:2014–08–02 19:00:57; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 20–01–14] $now:2014–08–02 20:01:14; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 21–00–55] $now:2014–08–02 21:00:55; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 22–01–07] $now:2014–08–02 22:01:07; $lastRunTime:2014–08–02 07:01:10
[2014–08–02 23–01–01] $now:2014–08–02 23:01:01; $lastRunTime:2014–08–02 07:01:10

[2014–08–03 07–01–00] $now:2014–08–03 07:01:00; $lastRunTime:2014–08–02 07:01:10
[2014–08–03 08–01–15] $now:2014–08–03 08:01:15; $lastRunTime:2014–08–02 07:01:10
[2014–08–03 08–01–16] saveRunTime:2014–08–03 08:01:16
[2014–08–03 09–00–52] $now:2014–08–03 09:00:52; $lastRunTime:2014–08–03 08:01:16
[2014–08–03 10–01–06] $now:2014–08–03 10:01:06; $lastRunTime:2014–08–03 08:01:16
[2014–08–03 11–00–51] $now:2014–08–03 11:00:51; $lastRunTime:2014–08–03 08:01:16
[2014–08–03 12–01–41] $now:2014–08–03 12:01:41; $lastRunTime:2014–08–03 08:01:16
[2014–08–03 13–00–52] $now:2014–08–03 13:00:52; $lastRunTime:2014–08–03 08:01:16
[2014–08–03 14–01–02] $now:2014–08–03 14:01:02; $lastRunTime:2014–08–03 08:01:16
[2014–08–03 15–01–01] $now:2014–08–03 15:01:01; $lastRunTime:2014–08–03 08:01:16
[2014–08–03 16–01–08] $now:2014–08–03 16:01:08; $lastRunTime:2014–08–03 08:01:16
[2014–08–03 17–00–50] $now:2014–08–03 17:00:50; $lastRunTime:2014–08–03 08:01:16
[2014–08–03 18–01–10] $now:2014–08–03 18:01:10; $lastRunTime:2014–08–03 08:01:16
[2014–08–03 19–00–52] $now:2014–08–03 19:00:52; $lastRunTime:2014–08–03 08:01:16
[2014–08–03 20–01–11] $now:2014–08–03 20:01:11; $lastRunTime:2014–08–03 08:01:16
[2014–08–03 21–00–54] $now:2014–08–03 21:00:54; $lastRunTime:2014–08–03 08:01:16
[2014–08–03 22–01–06] $now:2014–08–03 22:01:06; $lastRunTime:2014–08–03 08:01:16
[2014–08–03 23–01–00] $now:2014–08–03 23:01:00; $lastRunTime:2014–08–03 08:01:16

[2014–08–04 07–01–05] $now:2014–08–04 07:01:05; $lastRunTime:2014–08–03 08:01:16
[2014–08–04 08–01–12] $now:2014–08–04 08:01:12; $lastRunTime:2014–08–03 08:01:16
[2014–08–04 09–01–07] $now:2014–08–04 09:01:07; $lastRunTime:2014–08–03 08:01:16
[2014–08–04 09–01–07] saveRunTime:2014–08–04 09:01:07
[2014–08–04 10–01–08] $now:2014–08–04 10:01:07; $lastRunTime:2014–08–04 09:01:07
[2014–08–04 11–01–01] $now:2014–08–04 11:01:01; $lastRunTime:2014–08–04 09:01:07
[2014–08–04 12–01–43] $now:2014–08–04 12:01:43; $lastRunTime:2014–08–04 09:01:07
[2014–08–04 13–01–07] $now:2014–08–04 13:01:07; $lastRunTime:2014–08–04 09:01:07
[2014–08–04 14–01–15] $now:2014–08–04 14:01:15; $lastRunTime:2014–08–04 09:01:07
[2014–08–04 15–01–03] $now:2014–08–04 15:01:03; $lastRunTime:2014–08–04 09:01:07
[2014–08–04 16–01–12] $now:2014–08–04 16:01:12; $lastRunTime:2014–08–04 09:01:07
[2014–08–04 17–01–08] $now:2014–08–04 17:01:08; $lastRunTime:2014–08–04 09:01:07
[2014–08–04 18–01–20] $now:2014–08–04 18:01:19; $lastRunTime:2014–08–04 09:01:07
[2014–08–04 19–01–08] $now:2014–08–04 19:01:08; $lastRunTime:2014–08–04 09:01:07
[2014–08–04 20–01–13] $now:2014–08–04 20:01:13; $lastRunTime:2014–08–04 09:01:07
[2014–08–04 21–00–48] $now:2014–08–04 21:00:48; $lastRunTime:2014–08–04 09:01:07
[2014–08–04 22–01–12] $now:2014–08–04 22:01:12; $lastRunTime:2014–08–04 09:01:07
[2014–08–04 23–01–10] $now:2014–08–04 23:01:10; $lastRunTime:2014–08–04 09:01:07

Editoval duskohu (4. 8. 2014 23:27)

Azathoth
Člen | 495
+
0
-

Zdravím, plánuji použít cronner a chci se zeptat autora, jestli na něm plánuje v budoucnu pracovat a jestli je známo, kdy bychom se mohli těšit na verzi 1.0.0.

stekycz
Člen | 152
+
+1
-

@duskohu Omlouvám se zpožděnou odpověď. Založil jsem si na to issue. Měl bych se k tomu tento týden dostat.

@Azathoth S vydáním verze 1.0.0 otálím už asi měsíc nebo dva. Přemýšlím, jestli je ten pull request na paralelní běh ok. Zatím jsem ale nepřišel na lepší řešení nebo na možný problém, který by byl řešitelný. Za případné náměty budu rád :-) Plánuju na Cronneru pracovat, ale především co se týče opravách bugů. Sám totiž příliš nevím, jaké další funkce navíc by mohl někdo využít. Takže námětům i pull requestům se meze nekladou :-)

stekycz
Člen | 152
+
0
-

Tak konečně je verze 1.0.0 venku :-) Snad vše funguje bez problémů, případně zakládejte issue.

Stránka o Cronneru je už i na Addons.

@duskohu Tato verze by měla fixovat tvůj problém. Otestuj prosím a případně pošli feedback. Díky!

kolsi
Člen | 131
+
0
-

Šlape verze 1.0.0 i v Nette 2.0.15? Co jsem zkoušel nějakou poslední verzi, tak sice bylo napsáno, že podporuje Nette >= 2.0.0, ale nefungovalo to, protože byl závislý na třídách z Nette 2.2.

duskohu
Člen | 778
+
0
-

@stekycz cau , problem je v tom ze ja pouzivam aj verziu nette 2.1 pre tuto verziu to asi neni implementovane. Alebo je? A ak ano aky tag?

stekycz
Člen | 152
+
0
-

Snažím se v balíku držet maximum zpětné kompatibility. Sám ale jedu na nejnovějších stabilních verzích a tak mi může něco uniknout. Podle dotazů předpokládám, že bude stačit na pár míst přidat class_alias, protože většina problémů bude nejspíše kvůli rozdělení Nette na několik balíčků. Nicméně ze stejného důvodu pro tyto verze nejspíš vytvořím speciální tagy (kvůli závislostem). Založil jsem si issue, kouknu na to během týdne (možná už dnes).

Díky za reporting!

Azathoth
Člen | 495
+
0
-

Bohužel nejnovější verze padá, řádek 79, založil jsem to jako issue, ale dávám to pro jistotu i sem:
Notice

Undefined index: debugMode

File: …\vendor\stekycz\cronner\Cronner\DI\CronnerExtension.php:79

69: $config[‚criticalSectionTempDir‘]
70: ))
71: ->setAutowired(FALSE)
72: ->setInject(FALSE);
73:
74: $runner = $container->addDefinition($this->prefix(‚runner‘))
75: ->setClass(‚stekycz\Cronner\Cronner‘, array(
76: $storage,
77: $criticalSection,
78: $config[‚maxExecutionTime‘],
79: !$config[‚debugMode‘],
80: ));
81:
82: foreach (array_keys($container->findByTag(self::TASKS_TAG)) as $serviceName) {
83: $runner->addSetup(‚addTasks‘, array(‚@‘ . $serviceName));

stekycz
Člen | 152
+
+1
-

@Azathoth Díky za reporting! Už se pracuje na fixu :-)

stekycz
Člen | 152
+
+1
-

@kolsi @duskohu @Azathoth Mělo by být fixnuto. Verze 1.0.1 vydána a spolu s ní i speciální tagy pro Nette 2.0 se sufixem -n20 a pro Nette 2.1 se sufixem -n21

kolsi
Člen | 131
+
0
-

Já nevím, jestli mi něco uniká, ale při porovnání jsou master i v1.0.1-n2.0 shodné = tag pro Nette 2.0 obsahuje kód, který na verzi 2.0.15 nefunguje.

duskohu
Člen | 778
+
0
-

Ked mam: nette-2.1

cronner:
	timestampStorage: stekycz\Cronner\TimestampStorage\FileStorage(%wwwDir%/cronner)

tak to neprejde cez toto

Notice

Undefined property: stdClass::$entity

lebo v $config[‚timestampStorage‘] je:

stdClass #59e3
value => "stekycz\Cronner\TimestampStorage\FileStorage" (44)
attributes => array (1)
0 => "/data/web/virtuals/30211/virtual/www/subdom/agrokom-shop/www/cronner"

Editoval duskohu (20. 8. 2014 17:36)

Azathoth
Člen | 495
+
0
-

Ahoj,
tak se snažím rozchodit cronner a mám několik velmi hloupých otázek, tak mne prosím, neukamenujte:

  1. úplně jsem nepochopil ty tagy:

At the end you would need to specify your task objects. It would be some service with high probability. You can add tag cronner.tasks to all services with Cronner tasks and those services will be bind automatically. However you can still add new task objects by your own using addTasks method.

Kam konkrétně patří ten tag? Pochopil jsem to ta, že se to má dát do config.neon, ale úplně si nejsem jist, protože používání tagů v souvislosti s DI mi nějak uniklo.

  1. Musím nějak nastavovat cron, aby to fungovalo?
  2. co všechno je třeba udělat, aby to fungovalo? Přidat do config.neon cronner jako extension a potom jenom přidat do cronneru objekt, který má metody, které chci spouštět, pomocí toho tagu cronner.tasks a u těch metod pak stačí jenom v anotacích definovat, kdy se to má spouštět a to je všechno?

A nebo je lepší mít v presenteru jako závislost cronner, objekt, který má metody, které chci cronnerem spouštět a předat cronneru ten objekt pomocí $cronner->addTasks()?

Těším se na odpovědi.

Editoval Azathoth (28. 8. 2014 11:20)

Azathoth
Člen | 495
+
0
-

Případně pokud by někdo zveřejnil nějaké ukázkové soubory, tak bych byl úplně nejšťastnější.
@stekycz@duskohu

Editoval Azathoth (2. 9. 2014 16:50)

duskohu
Člen | 778
+
+2
-

@Azathoth neviam comu uplne nerozumies. Vsetko je pekne popisane v dokumentaci.
Ale skusim ti to zopakovat, aj ked len prepisujem to co uz niekto napisal:

1. zaregistrujes si extension v neone:

extension:
    cronner: stekycz\Cronner\DI\CronnerExtension

2. injectnes si cronner do presentru:

class CronPresenter extends \Nette\Application\UI\Presenter {
    /**
     * @var \stekycz\Cronner\Cronner
     * @inject
     */
    public $cronner;

    public function actionCron() {
        $this->cronner->run();
    }
}

3. mas moznost registrovat tasky v presentru, alebo v neone, ja si injectnem sluzby kde mam tasky do presentru a tam pridam cronerru tasky.

services:
	CronTasks
class CronPresenter extends \Nette\Application\UI\Presenter {
    /**
     * @var \stekycz\Cronner\Cronner
     * @inject
     */
    public $cronner;

    /**
     * @var CronTasks
     * @inject
     */
    public $cronTasks

    public function actionCron() {
		$this->cronner->addTasks($this->cronTasks);
        $this->cronner->run();
    }
}

4. Classa CronTasks ma tasky, tie definujes na zaklade anotaci:

class CronTasks {
    /**
     * @cronner-task E-mail sending
     * @cronner-period 1 day
     * @cronner-days working days
     * @cronner-time 23:30 - 05:00
     */
    public function sendEmails() {
        // Code which sends all your e-mails
    }

    /**
     * @cronner-task Important data replication
     * @cronner-period 3 hours
     */
    public function replicateImportantData() {
        // Replication code
    }
}
Azathoth
Člen | 495
+
0
-

Moc děkuji, ale ještě se chci ujistit:
Takže cron vůbec nijak nastavovat nemusím?
A ještě k tomu presenteru…zase hloupá otázka: já myslel, že actionCron se (pokud jsem správně pochopil nette) spustí pouze, když někdo navštíví www.example.com/Cron/cron. Nebo ne? A má na cronner nějaký vliv, když někdo navštíví nebo nenavštíví www.example.com/Cron/cron a zavolá se tedy actionCron?

stekycz
Člen | 152
+
+1
-

Omlouvám se za to, že jsem nereagoval, byl jsem na dovolené.

@duskohu Podívám se na problémy co nejdříve to půjde.

@Azathoth Cron nastavit musíš. Cronner je jen možnost, jak v rámci aplikace jednotlivé úkoly spravovat. Samotný běh můžeš pouštět buď v presenteru přes nějakou URL nebo přes CLI. Pro CLI mohu doporučit použití Kdyby/Console. Jak to použiješ záleží na tobě.

Hodně základních hostingů umožňuje pouštět Crona pouze po nějakém intervalu a pouze jednu úlohu. Přesně k tomu je Cronner určený. Navíc většina takovým akcí je pouštěná přes curl a proto je použití v presenteru možné. Nicméně pokud to potřebuješ takhle udělat, je vhodné filtrovat danou akci na IP adresy a pokud není localhost (nebo jiná povolená IP adresa), tak vrátit 404. Pokud použiješ CLI a Kdyby/Console, není filtr potřeba, ale většinou potřebuješ nastavit cron sám přes crontab.

Tagy u služby jsou metadata, které je možné použít třeba právě v jednotlivých rozšířeních. Je to součást nette/di. Teoreticky bych mohl nadefinovat interface bez metod a podle něj si služby vytahat. Nicméně je to zbytečná definice rozhraní a následně bys to musel ty implementovat. Mnohem jednodušší je přidat do configu k existující službě tag. Pokud tagy používat nechceš, musíš Cronneru předat jednotlivé objekty jak výše popisuje @duskohu. Tagy jsou v Cronneru až od verze 1.0.

duskohu
Člen | 778
+
0
-

@stekycz cau pozeral si sa na ten problem s nette-2.1 , nejaky pokrok?

Desttro
Člen | 126
+
0
-

ahoj, taky mám problém na nette 2.2.3: Undefined property: stdClass::$entity

Azathoth
Člen | 495
+
0
-

@Desttro Nechceš být trochu konkrétnější s tou chybou? Kde to vyskakuje?

Editoval Azathoth (15. 9. 2014 22:52)

David Matějka
Moderator | 6445
+
0
-

@duskohu @Desttro cronner nepouzivam, ale imho to pude vyresit prohnanim $config v compiler extension fci Compiler::filterArguments, tak si to zkuste a kdyztak poslete stekymu pull request

Desttro
Člen | 126
+
0
-

Azathoth napsal(a):

@Desttro Nechceš být trochu konkrétnější s tou chybou? Kde to vyskakuje?

duskohu napsal(a):

Ked mam: nette-2.1

cronner:
	timestampStorage: stekycz\Cronner\TimestampStorage\FileStorage(%wwwDir%/cronner)

tak to neprejde cez toto

Notice

Undefined property: stdClass::$entity

lebo v $config[‚timestampStorage‘] je:

stdClass #59e3
value => "stekycz\Cronner\TimestampStorage\FileStorage" (44)
attributes => array (1)
0 => "/data/web/virtuals/30211/virtual/www/subdom/agrokom-shop/www/cronner"
duskohu
Člen | 778
+
0
-

@matej21 pomohlo dik, prosim ta mohol by si mi objastnit, preco je nutne to pouzit, aky je rozdiel oproti nette 2.1?