How does Laravels task scheduling work without persisting the last completed date?Understanding Queues and Scheduler on Laravel 5.2Laravel Task Scheduling set to run every minute but it run only onceSynchonise Laravel Task Scheduler across multiple serversLaravel 5.3 scheduler Runs once on Windows then ends without any further processingHow to prevent laravel scheduler from running before the cron job is finishedLaravel Artisan: How does `schedule:run` work?How to Run Laravel Scheduler Cron Job Continuouslylaravel schedule async tasksHow to have multiple crontab entries for different projects Laravel task scheduling?Laravel custom command and Scheduler: how to know where the error stands
co-son-in-law or co-brother
Updating multiple vector points at once with vertex editor in QGIS?
Declaring 2 (or even multi-) dimensional std::arrays elegantly
Why do old games use flashing as means of showing damage?
In mathematics is there a substitution that is "different" from Vieta's substitution to solve the cubic equation?
Why don't they build airplanes from 3D printer plastic?
Can there be plants on the dark side of a tidally locked world?
Design of 50 ohms RF trace for 2.4GHz...Double layer FR-4 PCB
Is mathematics truth?
When making yogurt, why doesn't bad bacteria grow as well?
Strange LockTime values in Electrum transactions?
How to add some symbol (or just add newline) if the numbers in the text are not continuous
Would there be balance issues if I allowed opportunity attacks against any creature, not just hostile ones?
What is the maximal acceptable delay between pilot's input and flight control surface actuation?
Lumix G7: Raw photos only in 1920x1440, no higher res available
How Total raw is calculated for Science pack 2?
How do I stop making people jump at home and at work?
How to run a command 1 out of N times in Bash
Where is the correct position to set right or left of muscle names for anatomical names?
Are there any writings by blinded and/or exiled Byzantine emperors?
Which is the best password hashing algorithm in .NET Core?
Meaning of "educating the ice"
Heuristic argument for the Riemann Hypothesis
How to find better food in airports
How does Laravels task scheduling work without persisting the last completed date?
Understanding Queues and Scheduler on Laravel 5.2Laravel Task Scheduling set to run every minute but it run only onceSynchonise Laravel Task Scheduler across multiple serversLaravel 5.3 scheduler Runs once on Windows then ends without any further processingHow to prevent laravel scheduler from running before the cron job is finishedLaravel Artisan: How does `schedule:run` work?How to Run Laravel Scheduler Cron Job Continuouslylaravel schedule async tasksHow to have multiple crontab entries for different projects Laravel task scheduling?Laravel custom command and Scheduler: how to know where the error stands
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;
Laravel is (correctly) running scheduled tasks via the AppConsoleKernel@schedule
method. It does this without the need for a persistance layer. Previously ran scheduled tasks aren't saved to the database or stored in anyway.
How is this "magic" achieved? I want to have a deeper understanding.
I have looked through the source, and I can see it is somewhat achieved by rounding down the current date and diffing that to the schedule frequency, along with the fact that it is required to run every minute, it can say with a certain level of confidence that it should run a task. That is my interpretation, but I still can't fully grasp how it is guaranteeing to run on schedule and how it handles failure or things being off by a few seconds.
EDIT Edit due to clarity issue pointed out in comment.
By "a few seconds" I mean how does the "round down" method work, even when it is ran every minute, but not at the same second - example: first run 00:01.00, 00:01:02, 00:02:04
Maybe to clarify further, and to assist in understanding how it works, is there any boundary guarantees on how it functions? If ran multiple times per minute will it execute per minute tasks multiple times in the minute?
laravel
add a comment |
Laravel is (correctly) running scheduled tasks via the AppConsoleKernel@schedule
method. It does this without the need for a persistance layer. Previously ran scheduled tasks aren't saved to the database or stored in anyway.
How is this "magic" achieved? I want to have a deeper understanding.
I have looked through the source, and I can see it is somewhat achieved by rounding down the current date and diffing that to the schedule frequency, along with the fact that it is required to run every minute, it can say with a certain level of confidence that it should run a task. That is my interpretation, but I still can't fully grasp how it is guaranteeing to run on schedule and how it handles failure or things being off by a few seconds.
EDIT Edit due to clarity issue pointed out in comment.
By "a few seconds" I mean how does the "round down" method work, even when it is ran every minute, but not at the same second - example: first run 00:01.00, 00:01:02, 00:02:04
Maybe to clarify further, and to assist in understanding how it works, is there any boundary guarantees on how it functions? If ran multiple times per minute will it execute per minute tasks multiple times in the minute?
laravel
Do you have any insight into the per-second schedule guarantee you mention, I can't find any reference to it and haven't heard of such a thing before. As far as I know, it is not guaranteed that your command will run with to the second accuracy nor is it guaranteed it won't run twice if you runschedule:run
twice in a minute. The scheduler knows that it is run every minute, it knows what the current time is, so if you say "every 10 minutes" the scheduler knows "if the current minute is 0, 10, 20, 30, 40, 50 then run the task".
– sam
Mar 28 at 2:34
Poor wording on my behalf - I will update "few seconds" - cheers Sam
– Chris
Mar 28 at 2:56
Looking at the tests we can get a rough idea of what is expected behaviour. There does look to be some persistence, as there are tests to ensure the same command isn't ran more than once. Generally tests are a good place to look to understand how something is expected to behave.
– sam
Mar 28 at 3:08
I agree - and the level of persistence is what I am trying to understand
– Chris
Mar 28 at 5:28
The tests looking interesting, esp the frequency one. Its testing against a cron signature, so curious if the schedule task receives the cron params and checks if it matches?
– Chris
Mar 28 at 5:32
add a comment |
Laravel is (correctly) running scheduled tasks via the AppConsoleKernel@schedule
method. It does this without the need for a persistance layer. Previously ran scheduled tasks aren't saved to the database or stored in anyway.
How is this "magic" achieved? I want to have a deeper understanding.
I have looked through the source, and I can see it is somewhat achieved by rounding down the current date and diffing that to the schedule frequency, along with the fact that it is required to run every minute, it can say with a certain level of confidence that it should run a task. That is my interpretation, but I still can't fully grasp how it is guaranteeing to run on schedule and how it handles failure or things being off by a few seconds.
EDIT Edit due to clarity issue pointed out in comment.
By "a few seconds" I mean how does the "round down" method work, even when it is ran every minute, but not at the same second - example: first run 00:01.00, 00:01:02, 00:02:04
Maybe to clarify further, and to assist in understanding how it works, is there any boundary guarantees on how it functions? If ran multiple times per minute will it execute per minute tasks multiple times in the minute?
laravel
Laravel is (correctly) running scheduled tasks via the AppConsoleKernel@schedule
method. It does this without the need for a persistance layer. Previously ran scheduled tasks aren't saved to the database or stored in anyway.
How is this "magic" achieved? I want to have a deeper understanding.
I have looked through the source, and I can see it is somewhat achieved by rounding down the current date and diffing that to the schedule frequency, along with the fact that it is required to run every minute, it can say with a certain level of confidence that it should run a task. That is my interpretation, but I still can't fully grasp how it is guaranteeing to run on schedule and how it handles failure or things being off by a few seconds.
EDIT Edit due to clarity issue pointed out in comment.
By "a few seconds" I mean how does the "round down" method work, even when it is ran every minute, but not at the same second - example: first run 00:01.00, 00:01:02, 00:02:04
Maybe to clarify further, and to assist in understanding how it works, is there any boundary guarantees on how it functions? If ran multiple times per minute will it execute per minute tasks multiple times in the minute?
laravel
laravel
edited Mar 28 at 3:00
Chris
asked Mar 28 at 1:44
ChrisChris
23.7k17 gold badges97 silver badges148 bronze badges
23.7k17 gold badges97 silver badges148 bronze badges
Do you have any insight into the per-second schedule guarantee you mention, I can't find any reference to it and haven't heard of such a thing before. As far as I know, it is not guaranteed that your command will run with to the second accuracy nor is it guaranteed it won't run twice if you runschedule:run
twice in a minute. The scheduler knows that it is run every minute, it knows what the current time is, so if you say "every 10 minutes" the scheduler knows "if the current minute is 0, 10, 20, 30, 40, 50 then run the task".
– sam
Mar 28 at 2:34
Poor wording on my behalf - I will update "few seconds" - cheers Sam
– Chris
Mar 28 at 2:56
Looking at the tests we can get a rough idea of what is expected behaviour. There does look to be some persistence, as there are tests to ensure the same command isn't ran more than once. Generally tests are a good place to look to understand how something is expected to behave.
– sam
Mar 28 at 3:08
I agree - and the level of persistence is what I am trying to understand
– Chris
Mar 28 at 5:28
The tests looking interesting, esp the frequency one. Its testing against a cron signature, so curious if the schedule task receives the cron params and checks if it matches?
– Chris
Mar 28 at 5:32
add a comment |
Do you have any insight into the per-second schedule guarantee you mention, I can't find any reference to it and haven't heard of such a thing before. As far as I know, it is not guaranteed that your command will run with to the second accuracy nor is it guaranteed it won't run twice if you runschedule:run
twice in a minute. The scheduler knows that it is run every minute, it knows what the current time is, so if you say "every 10 minutes" the scheduler knows "if the current minute is 0, 10, 20, 30, 40, 50 then run the task".
– sam
Mar 28 at 2:34
Poor wording on my behalf - I will update "few seconds" - cheers Sam
– Chris
Mar 28 at 2:56
Looking at the tests we can get a rough idea of what is expected behaviour. There does look to be some persistence, as there are tests to ensure the same command isn't ran more than once. Generally tests are a good place to look to understand how something is expected to behave.
– sam
Mar 28 at 3:08
I agree - and the level of persistence is what I am trying to understand
– Chris
Mar 28 at 5:28
The tests looking interesting, esp the frequency one. Its testing against a cron signature, so curious if the schedule task receives the cron params and checks if it matches?
– Chris
Mar 28 at 5:32
Do you have any insight into the per-second schedule guarantee you mention, I can't find any reference to it and haven't heard of such a thing before. As far as I know, it is not guaranteed that your command will run with to the second accuracy nor is it guaranteed it won't run twice if you run
schedule:run
twice in a minute. The scheduler knows that it is run every minute, it knows what the current time is, so if you say "every 10 minutes" the scheduler knows "if the current minute is 0, 10, 20, 30, 40, 50 then run the task".– sam
Mar 28 at 2:34
Do you have any insight into the per-second schedule guarantee you mention, I can't find any reference to it and haven't heard of such a thing before. As far as I know, it is not guaranteed that your command will run with to the second accuracy nor is it guaranteed it won't run twice if you run
schedule:run
twice in a minute. The scheduler knows that it is run every minute, it knows what the current time is, so if you say "every 10 minutes" the scheduler knows "if the current minute is 0, 10, 20, 30, 40, 50 then run the task".– sam
Mar 28 at 2:34
Poor wording on my behalf - I will update "few seconds" - cheers Sam
– Chris
Mar 28 at 2:56
Poor wording on my behalf - I will update "few seconds" - cheers Sam
– Chris
Mar 28 at 2:56
Looking at the tests we can get a rough idea of what is expected behaviour. There does look to be some persistence, as there are tests to ensure the same command isn't ran more than once. Generally tests are a good place to look to understand how something is expected to behave.
– sam
Mar 28 at 3:08
Looking at the tests we can get a rough idea of what is expected behaviour. There does look to be some persistence, as there are tests to ensure the same command isn't ran more than once. Generally tests are a good place to look to understand how something is expected to behave.
– sam
Mar 28 at 3:08
I agree - and the level of persistence is what I am trying to understand
– Chris
Mar 28 at 5:28
I agree - and the level of persistence is what I am trying to understand
– Chris
Mar 28 at 5:28
The tests looking interesting, esp the frequency one. Its testing against a cron signature, so curious if the schedule task receives the cron params and checks if it matches?
– Chris
Mar 28 at 5:32
The tests looking interesting, esp the frequency one. Its testing against a cron signature, so curious if the schedule task receives the cron params and checks if it matches?
– Chris
Mar 28 at 5:32
add a comment |
1 Answer
1
active
oldest
votes
Cronjob can not guarantee seconds precisely. That is why generally no cronjob interval is less than a minute. So, in reality, it doesn't handle "things being off by a few seconds."
What happens in laravel is this, after running scheduling command for the first time the server asks "Is there a queued job?" every minute. If none, it doesn't do anything.
For example, take the "daily" cronjob. Scheduler doesn't need to know when was the last time it ran the task or something like this. When it encounters the daily cronjob it simply checks if it is midnight. If it is midnight it runs the job.
Also, take "every thirty minute" cronjob. Maybe you registered the cronjob at 10:25. But still the first time it will run on 10:30, not on 10:55. It doesn't care what time you registered or when was the last time it ran. It only checks if the current minute is "00" or divisible by thirty. So at 10:30 it will run. Again, it will run on 11:00. and so on.
Similarly a ten minute cronjob by default will only check if the current minute is divisible by ten or not. So, regardless of the time you registered the command it will run only on XX:00, XX:10, XX:20 and so on.
That is why by default it doesn't need to store previously ran scheduled task. However, you can store it into a file if you want for monitoring purpose.
The first bit makes (midnight), and I was about to tick it as the answer. But the second bit throws me again - how does it know on the first time to run at 10:30 and not the next time 10:25 swings around again
– Chris
Mar 28 at 5:30
1
Check my answer now. Hope it makes sense now.
– Nabil Farhan
Mar 28 at 6:05
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55388975%2fhow-does-laravels-task-scheduling-work-without-persisting-the-last-completed-dat%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
Cronjob can not guarantee seconds precisely. That is why generally no cronjob interval is less than a minute. So, in reality, it doesn't handle "things being off by a few seconds."
What happens in laravel is this, after running scheduling command for the first time the server asks "Is there a queued job?" every minute. If none, it doesn't do anything.
For example, take the "daily" cronjob. Scheduler doesn't need to know when was the last time it ran the task or something like this. When it encounters the daily cronjob it simply checks if it is midnight. If it is midnight it runs the job.
Also, take "every thirty minute" cronjob. Maybe you registered the cronjob at 10:25. But still the first time it will run on 10:30, not on 10:55. It doesn't care what time you registered or when was the last time it ran. It only checks if the current minute is "00" or divisible by thirty. So at 10:30 it will run. Again, it will run on 11:00. and so on.
Similarly a ten minute cronjob by default will only check if the current minute is divisible by ten or not. So, regardless of the time you registered the command it will run only on XX:00, XX:10, XX:20 and so on.
That is why by default it doesn't need to store previously ran scheduled task. However, you can store it into a file if you want for monitoring purpose.
The first bit makes (midnight), and I was about to tick it as the answer. But the second bit throws me again - how does it know on the first time to run at 10:30 and not the next time 10:25 swings around again
– Chris
Mar 28 at 5:30
1
Check my answer now. Hope it makes sense now.
– Nabil Farhan
Mar 28 at 6:05
add a comment |
Cronjob can not guarantee seconds precisely. That is why generally no cronjob interval is less than a minute. So, in reality, it doesn't handle "things being off by a few seconds."
What happens in laravel is this, after running scheduling command for the first time the server asks "Is there a queued job?" every minute. If none, it doesn't do anything.
For example, take the "daily" cronjob. Scheduler doesn't need to know when was the last time it ran the task or something like this. When it encounters the daily cronjob it simply checks if it is midnight. If it is midnight it runs the job.
Also, take "every thirty minute" cronjob. Maybe you registered the cronjob at 10:25. But still the first time it will run on 10:30, not on 10:55. It doesn't care what time you registered or when was the last time it ran. It only checks if the current minute is "00" or divisible by thirty. So at 10:30 it will run. Again, it will run on 11:00. and so on.
Similarly a ten minute cronjob by default will only check if the current minute is divisible by ten or not. So, regardless of the time you registered the command it will run only on XX:00, XX:10, XX:20 and so on.
That is why by default it doesn't need to store previously ran scheduled task. However, you can store it into a file if you want for monitoring purpose.
The first bit makes (midnight), and I was about to tick it as the answer. But the second bit throws me again - how does it know on the first time to run at 10:30 and not the next time 10:25 swings around again
– Chris
Mar 28 at 5:30
1
Check my answer now. Hope it makes sense now.
– Nabil Farhan
Mar 28 at 6:05
add a comment |
Cronjob can not guarantee seconds precisely. That is why generally no cronjob interval is less than a minute. So, in reality, it doesn't handle "things being off by a few seconds."
What happens in laravel is this, after running scheduling command for the first time the server asks "Is there a queued job?" every minute. If none, it doesn't do anything.
For example, take the "daily" cronjob. Scheduler doesn't need to know when was the last time it ran the task or something like this. When it encounters the daily cronjob it simply checks if it is midnight. If it is midnight it runs the job.
Also, take "every thirty minute" cronjob. Maybe you registered the cronjob at 10:25. But still the first time it will run on 10:30, not on 10:55. It doesn't care what time you registered or when was the last time it ran. It only checks if the current minute is "00" or divisible by thirty. So at 10:30 it will run. Again, it will run on 11:00. and so on.
Similarly a ten minute cronjob by default will only check if the current minute is divisible by ten or not. So, regardless of the time you registered the command it will run only on XX:00, XX:10, XX:20 and so on.
That is why by default it doesn't need to store previously ran scheduled task. However, you can store it into a file if you want for monitoring purpose.
Cronjob can not guarantee seconds precisely. That is why generally no cronjob interval is less than a minute. So, in reality, it doesn't handle "things being off by a few seconds."
What happens in laravel is this, after running scheduling command for the first time the server asks "Is there a queued job?" every minute. If none, it doesn't do anything.
For example, take the "daily" cronjob. Scheduler doesn't need to know when was the last time it ran the task or something like this. When it encounters the daily cronjob it simply checks if it is midnight. If it is midnight it runs the job.
Also, take "every thirty minute" cronjob. Maybe you registered the cronjob at 10:25. But still the first time it will run on 10:30, not on 10:55. It doesn't care what time you registered or when was the last time it ran. It only checks if the current minute is "00" or divisible by thirty. So at 10:30 it will run. Again, it will run on 11:00. and so on.
Similarly a ten minute cronjob by default will only check if the current minute is divisible by ten or not. So, regardless of the time you registered the command it will run only on XX:00, XX:10, XX:20 and so on.
That is why by default it doesn't need to store previously ran scheduled task. However, you can store it into a file if you want for monitoring purpose.
edited Mar 28 at 6:07
answered Mar 28 at 3:45
Nabil FarhanNabil Farhan
9206 silver badges21 bronze badges
9206 silver badges21 bronze badges
The first bit makes (midnight), and I was about to tick it as the answer. But the second bit throws me again - how does it know on the first time to run at 10:30 and not the next time 10:25 swings around again
– Chris
Mar 28 at 5:30
1
Check my answer now. Hope it makes sense now.
– Nabil Farhan
Mar 28 at 6:05
add a comment |
The first bit makes (midnight), and I was about to tick it as the answer. But the second bit throws me again - how does it know on the first time to run at 10:30 and not the next time 10:25 swings around again
– Chris
Mar 28 at 5:30
1
Check my answer now. Hope it makes sense now.
– Nabil Farhan
Mar 28 at 6:05
The first bit makes (midnight), and I was about to tick it as the answer. But the second bit throws me again - how does it know on the first time to run at 10:30 and not the next time 10:25 swings around again
– Chris
Mar 28 at 5:30
The first bit makes (midnight), and I was about to tick it as the answer. But the second bit throws me again - how does it know on the first time to run at 10:30 and not the next time 10:25 swings around again
– Chris
Mar 28 at 5:30
1
1
Check my answer now. Hope it makes sense now.
– Nabil Farhan
Mar 28 at 6:05
Check my answer now. Hope it makes sense now.
– Nabil Farhan
Mar 28 at 6:05
add a comment |
Got a question that you can’t ask on public Stack Overflow? Learn more about sharing private information with Stack Overflow for Teams.
Got a question that you can’t ask on public Stack Overflow? Learn more about sharing private information with Stack Overflow for Teams.
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55388975%2fhow-does-laravels-task-scheduling-work-without-persisting-the-last-completed-dat%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Do you have any insight into the per-second schedule guarantee you mention, I can't find any reference to it and haven't heard of such a thing before. As far as I know, it is not guaranteed that your command will run with to the second accuracy nor is it guaranteed it won't run twice if you run
schedule:run
twice in a minute. The scheduler knows that it is run every minute, it knows what the current time is, so if you say "every 10 minutes" the scheduler knows "if the current minute is 0, 10, 20, 30, 40, 50 then run the task".– sam
Mar 28 at 2:34
Poor wording on my behalf - I will update "few seconds" - cheers Sam
– Chris
Mar 28 at 2:56
Looking at the tests we can get a rough idea of what is expected behaviour. There does look to be some persistence, as there are tests to ensure the same command isn't ran more than once. Generally tests are a good place to look to understand how something is expected to behave.
– sam
Mar 28 at 3:08
I agree - and the level of persistence is what I am trying to understand
– Chris
Mar 28 at 5:28
The tests looking interesting, esp the frequency one. Its testing against a cron signature, so curious if the schedule task receives the cron params and checks if it matches?
– Chris
Mar 28 at 5:32