-
Notifications
You must be signed in to change notification settings - Fork 242
Add comments to aec_block_generator #4529
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,27 @@ | ||
%%%============================================================================= | ||
%%% @copyright 2018, Aeternity Anstalt | ||
%%% @doc | ||
%%% Module that handle candidate block generation. | ||
%%% Server for collecting transactions into microblocks | ||
%%% @end | ||
%%%============================================================================= | ||
-module(aec_block_generator). | ||
|
||
%% The server builds one microbolock at a time, starting with transactions | ||
%% already in the transaction pool. It also subscribes to events about new | ||
%% transactions, adding them to the current microblock as they arrive, | ||
%% avoiding having to query the pool again. | ||
%% | ||
%% When the chain top changes, the current microblock candidate is | ||
%% discarded and a new candidate microblock is started. | ||
%% | ||
%% When a candidate is ready, this fact is published as a `candidate_block` | ||
%% event, and the `get_candidate()` API function can be used to fetch the | ||
%% current candidate (see the `aec_conductor` module). There is however no | ||
%% guarantee that the candidate has not been discarded in the meantime. | ||
%% | ||
%% A single worker process is used for doing the heavy work in the | ||
%% background, for better responsiveness. | ||
|
||
%% API | ||
-export([start_link/0, stop/0]). | ||
|
||
|
@@ -91,13 +107,13 @@ handle_cast(start_generation, State) -> | |
{noreply, do_start_generation(State)}; | ||
handle_cast({worker_done, Pid, {candidate, Candidate, CandidateState}}, | ||
State = #state{ worker = {Pid, _} }) -> | ||
%% Only publish non-empty micro-blocks | ||
%% Only publish non-empty microblocks | ||
case aec_blocks:txs(Candidate) of | ||
[] -> | ||
lager:debug("New empty microblock candidate generated, and discarded", []), | ||
lager:debug("Empty microblock candidate prepared", []), | ||
ok; | ||
_ -> | ||
epoch_mining:info("New microblock candidate generated", []), | ||
epoch_mining:info("New microblock candidate ready", []), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not an improvement? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, it's getting published as being ready to fetch. Tried to make it contrast with the alternative message above |
||
publish_candidate(Candidate) | ||
end, | ||
State1 = finish_worker(State), | ||
|
@@ -149,6 +165,7 @@ code_change(_OldVsn, State, _Extra) -> | |
|
||
%% -- Local functions -------------------------------------------------------- | ||
|
||
%% When server is told to start generation | ||
do_start_generation(S = #state{ generating = false }) -> | ||
S1 = start_worker(S), | ||
S1#state{ generating = true }; | ||
|
@@ -158,25 +175,30 @@ do_start_generation(S = #state{ candidate = Candidate }) -> | |
|| Candidate /= undefined andalso aec_blocks:txs(Candidate) /= [] ], | ||
S. | ||
|
||
%% When server is told to stop generation | ||
do_stop_generation(S = #state{ generating = true }) -> | ||
S1 = stop_worker(S), | ||
S1#state{ generating = false }; | ||
do_stop_generation(S) -> | ||
S. | ||
|
||
%% When a new transaction arrives, either cache it until the current worker | ||
%% has finished, or start a new worker to add it right away. | ||
add_new_tx(S = #state{ worker = Worker }, Tx) -> | ||
case Worker of | ||
undefined -> start_worker_txs(S, [Tx]); | ||
{_WPid, _WRef} -> S#state{ new_txs = [Tx | S#state.new_txs] } | ||
end. | ||
|
||
%% Terminate current worker and start a new one | ||
%% (used when the top has changed) | ||
preempt_generation(S, #{ block_hash := NewTop }) -> | ||
S1 = stop_worker(S), | ||
start_worker_block(S1, NewTop). | ||
|
||
stop_worker(S = #state{ worker = {WPid, WRef} }) -> | ||
erlang:demonitor(WRef, [flush]), | ||
erlang:exit(WPid, finished), | ||
erlang:exit(WPid, finished), % kill worker process | ||
lager:debug("stopped worker ~p", [WPid]), | ||
S#state{ worker = undefined }; | ||
stop_worker(S) -> | ||
|
@@ -190,34 +212,50 @@ worker_failed(Reason, S) -> | |
S1 = finish_worker(S), | ||
start_worker(S1). | ||
|
||
%% Update server side bookkeeping when worker is already terminated | ||
finish_worker(S = #state{ worker = {_WPid, WRef} }) -> | ||
erlang:demonitor(WRef, [flush]), | ||
S#state{ worker = undefined }. | ||
|
||
%% Creates a fresh microblock candidate on the current top block (used when | ||
%% generation goes from stopped to started) | ||
start_worker(S) -> | ||
case aec_chain:top_block() of | ||
undefined -> S; | ||
Block -> start_worker_block(S, Block) | ||
end. | ||
|
||
%% Creates a fresh microblock candidate by querying the transaction pool. | ||
%% Used when the top has changed; there must be no current worker running. | ||
%% Discards and clears the new_tx cache and sets the current candidate to | ||
%% `undefined` so any previously existing candidate can no longer be | ||
%% fetched. | ||
start_worker_block(S = #state{ worker = undefined }, BlockOrBlockHash) -> | ||
{Pid, Ref} = spawn_monitor(fun() -> create_block_candidate(BlockOrBlockHash) end), | ||
lager:debug("Worker ~p created", [Pid]), | ||
S#state{ worker = {Pid, Ref}, new_txs = [], candidate = undefined }. | ||
|
||
%% Starts a worker to add transactions from the new_tx cache to the current | ||
%% candidate and clear the new_tx cache. There must be no current worker | ||
%% running. The worker will get the current candidate microblock, returning | ||
%% the updated candidate when done. | ||
start_worker_txs(S = #state{ worker = undefined, candidate = Candidate | ||
, candidate_state = CState }, Txs) -> | ||
, candidate_state = CState }, Txs) | ||
when Candidate =/= undefined -> | ||
{Pid, Ref} = spawn_monitor(fun() -> update_block_candidate(Candidate, CState, Txs) end), | ||
lager:debug("Worker ~p created", [Pid]), | ||
S#state{ worker = {Pid, Ref}, new_txs = [] }. | ||
|
||
%% Used when the worker has finished but we may have cached transactions | ||
%% and if so start a new worker to handle them | ||
maybe_start_worker_txs(S) -> | ||
case S#state.new_txs of | ||
[] -> S; | ||
Txs -> start_worker_txs(S, Txs) | ||
end. | ||
|
||
%% Generate block candidate | ||
%% Worker: generate a fresh microblock candidate, taking transactions | ||
%% from the transaction pool | ||
create_block_candidate(BlockOrBlockHash) -> | ||
case aec_block_micro_candidate:create(BlockOrBlockHash) of | ||
{ok, NewBlock, NewBlockInfo} -> | ||
|
@@ -227,6 +265,7 @@ create_block_candidate(BlockOrBlockHash) -> | |
end, | ||
ok. | ||
|
||
%% Worker: take a microblock candidate and update it, adding transactions | ||
update_block_candidate(Block, BlockInfo, Txs) -> | ||
case aec_block_micro_candidate:update(Block, Txs, BlockInfo) of | ||
{ok, NewBlock, NewBlockInfo} -> | ||
|
@@ -235,9 +274,11 @@ update_block_candidate(Block, BlockInfo, Txs) -> | |
failed_attempt(Reason) | ||
end. | ||
|
||
%% Report error and terminate worker | ||
failed_attempt(Reason) -> | ||
gen_server:cast(?MODULE, {worker_done, self(), {failed, Reason}}). | ||
|
||
%% Report new candidate microblock and terminate worker | ||
new_candidate(NewBlock, NewBlockInfo) -> | ||
gen_server:cast(?MODULE, {worker_done, self(), {candidate, NewBlock, NewBlockInfo}}). | ||
|
||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
discarded
was the important bit here, I don't like it being removed from the message.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying to find a wording that made sense. It's not discarded, just not published, and stays in the state ready to be filled when there are incoming tx events. Any better suggestion?