1 %%%-------------------------------------------------------------------
2 %%% File : etorrent_t_peer_group.erl
3 %%% Author : Jesper Louis Andersen <jesper.louis.andersen@gmail.com>
4 %%% License : See COPYING
5 %%% Description : Master process for a number of peers.
7 %%% Created : 18 Jul 2007 by
8 %%% Jesper Louis Andersen <jesper.louis.andersen@gmail.com>
9 %%%-------------------------------------------------------------------
10 -module(etorrent_choker
).
12 -behaviour(gen_server
).
14 -include("rate_mgr.hrl").
15 -include("peer_state.hrl").
17 -include("etorrent_mnesia_table.hrl").
20 -export([start_link
/1, perform_rechoke
/0, monitor
/1]).
22 %% gen_server callbacks
23 -export([init
/1, handle_call
/3, handle_cast
/2, handle_info
/2,
24 terminate
/2, code_change
/3]).
26 -record(state
, {our_peer_id
= none
,
32 optimistic_unchoke_pid
= none
,
33 opt_unchoke_chain
= []}).
35 -record(rechoke_info
, {pid :: pid(),
36 kind
:: 'seeding' | 'leeching',
37 state
:: 'seeding' | 'leeching' ,
39 r_interest_state
:: 'interested' | 'not_interested',
40 r_choke_state
:: 'choked' | 'unchoked' ,
44 -define(SERVER
, ?MODULE
).
46 -define(ROUND_TIME
, 10000).
48 %%====================================================================
50 %%====================================================================
51 start_link(OurPeerId
) ->
52 gen_server:start_link({local
, ?SERVER
}, ?MODULE
, [OurPeerId
], []).
55 gen_server:cast(?SERVER
, rechoke
).
58 gen_server:call(?SERVER
, {monitor
, Pid
}).
60 %%====================================================================
61 %% gen_server callbacks
62 %%====================================================================
65 process_flag(trap_exit
, true
),
66 {ok
, Tref
} = timer:send_interval(?ROUND_TIME
, self(), round_tick
),
67 {ok
, #state
{ our_peer_id
= OurPeerId
,
70 handle_call({monitor
, Pid
}, _From
, S
) ->
71 _Tref
= erlang:monitor(process, Pid
),
72 NewChain
= insert_new_peer_into_chain(Pid
, S#state
.opt_unchoke_chain
),
74 {reply
, ok
, S#state
{ opt_unchoke_chain
= NewChain
}};
75 handle_call(Request
, _From
, State
) ->
76 error_logger:error_report([unknown_peer_group_call
, Request
]),
78 {reply
, Reply
, State
}.
79 handle_cast(rechoke
, S
) ->
82 handle_cast(_Msg
, State
) ->
85 handle_info(round_tick
, S
) ->
88 {ok
, NS
} = advance_optimistic_unchoke(S
),
90 {noreply
, NS#state
{ round = 2}};
91 N
when is_integer(N
) ->
93 {noreply
, S#state
{round = S#state
.round - 1}}
95 handle_info({'DOWN', _Ref
, process, Pid
, Reason
}, S
)
96 when (Reason
=:= normal
) or (Reason
=:= shutdown
) ->
97 % The peer shut down normally. Hence we just remove him and start up
98 % other peers. Eventually the tracker will re-add him to the peer list
100 % XXX: We might have to do something else
103 NewChain
= lists:delete(Pid
, S#state
.opt_unchoke_chain
),
104 {noreply
, S#state
{ opt_unchoke_chain
= NewChain
}};
105 handle_info({'DOWN', _Ref
, process, Pid
, _Reason
}, S
) ->
106 % The peer shut down unexpectedly re-add him to the queue in the *back*
107 NS
= case etorrent_peer:select(Pid
) of
109 % XXX: We might have to check that remote is intersted and we were choking
114 NewChain
= lists:delete(Pid
, NS#state
.opt_unchoke_chain
),
115 {noreply
, NS#state
{opt_unchoke_chain
= NewChain
}};
116 handle_info(Info
, State
) ->
117 error_logger:error_report([unknown_info_peer_group
, Info
]),
120 terminate(_Reason
, _S
) ->
123 code_change(_OldVsn
, State
, _Extra
) ->
126 %%--------------------------------------------------------------------
127 %%% Internal functions
128 %%--------------------------------------------------------------------
130 %%--------------------------------------------------------------------
131 %% Function: rechoke(State) -> ok
132 %% Description: Recalculate the choke/unchoke state of peers
133 %%--------------------------------------------------------------------
135 Peers
= build_rechoke_info(S#state
.opt_unchoke_chain
),
136 {PreferredDown
, PreferredSeed
} = split_preferred(Peers
),
137 PreferredSet
= prune_preferred_peers(PreferredDown
, PreferredSeed
),
138 ToChoke
= rechoke_unchoke(Peers
, PreferredSet
),
139 rechoke_choke(ToChoke
, 0, optimistics(PreferredSet
)).
141 build_rechoke_info(Peers
) ->
142 SeederSet
= sets:from_list(seeding_torrents()),
143 build_rechoke_info(SeederSet
, Peers
).
145 build_rechoke_info(_Seeding
, []) ->
147 build_rechoke_info(Seeding
, [Pid
| Next
]) ->
148 case etorrent_peer:select(Pid
) of
149 [] -> build_rechoke_info(Seeding
, Next
);
151 Kind
= Peer#peer
.state
,
152 Snubbed
= etorrent_rate_mgr:snubbed(Peer#peer
.torrent_id
, Pid
),
153 PeerState
= etorrent_rate_mgr:select_state(Peer#peer
.torrent_id
, Pid
),
154 case sets:is_element(Peer#peer
.torrent_id
, Seeding
) of
156 case etorrent_rate_mgr:fetch_send_rate(
157 Peer#peer
.torrent_id
,
159 none
-> build_rechoke_info(Seeding
, Next
);
161 [#rechoke_info
{ pid = Pid
,
166 PeerState#peer_state
.interest_state
,
168 PeerState#peer_state
.choke_state
,
170 PeerState#peer_state
.local_choke
,
171 snubbed
= Snubbed
} |
172 build_rechoke_info(Seeding
, Next
)]
175 case etorrent_rate_mgr:fetch_recv_rate(
176 Peer#peer
.torrent_id
,
178 none
-> build_rechoke_info(Seeding
, Next
);
180 [#rechoke_info
{ pid = Pid
,
183 rate
= -Rate
, % Inverted for later sorting!
185 PeerState#peer_state
.interest_state
,
187 PeerState#peer_state
.choke_state
,
189 PeerState#peer_state
.local_choke
,
190 snubbed
= Snubbed
} |
191 build_rechoke_info(Seeding
, Next
)]
196 advance_optimistic_unchoke(S
) ->
197 NewChain
= move_cyclic_chain(S#state
.opt_unchoke_chain
),
200 {ok
, S
}; %% No peers yet
202 etorrent_t_peer_recv:unchoke(H
),
203 {ok
, S#state
{ opt_unchoke_chain
= NewChain
,
204 optimistic_unchoke_pid
= H
}}
207 move_cyclic_chain([]) -> [];
208 move_cyclic_chain(Chain
) ->
210 case etorrent_peer:select(Pid
) of
212 [P
] -> T
= etorrent_rate_mgr:select_state(
215 not (T#peer_state
.interest_state
=:= interested
216 andalso T#peer_state
.choke_state
=:= choked
)
219 {Front
, Back
} = lists:splitwith(F
, Chain
),
223 insert_new_peer_into_chain(Pid
, Chain
) ->
224 Length
= length(Chain
),
225 Index
= lists:max([0, crypto:rand_uniform(0, Length
)]),
226 {Front
, Back
} = lists:split(Index
, Chain
),
227 Front
++ [Pid
| Back
].
230 case application:get_env(etorrent
, max_upload_slots
) of
232 {ok
, Rate
} = application:get_env(etorrent
, max_upload_rate
),
234 N
when N
=< 0 -> 7; %% Educated guess
239 round(math:sqrt(N
* 0.6))
241 {ok
, N
} when is_integer(N
) ->
245 split_preferred(Peers
) ->
246 {Downs
, Leechs
} = split_preferred_peers(Peers
, [], []),
247 {lists:keysort(#rechoke_info
.rate
, Downs
),
248 lists:keysort(#rechoke_info
.rate
, Leechs
)}.
250 prune_preferred_peers(SDowns
, SLeechs
) ->
251 MaxUploads
= upload_slots(),
252 DUploads
= lists:max([1, round(MaxUploads
* 0.7)]),
253 SUploads
= lists:max([1, round(MaxUploads
* 0.3)]),
255 case lists:max([0, DUploads
- length(SDowns
)]) of
256 0 -> {SUploads
, DUploads
};
257 N
-> {SUploads
+ N
, DUploads
- N
}
260 case lists:max([0, SUP2
- length(SLeechs
)]) of
263 {SUP2
- K
, lists:min([DUP2
+ K
, length(SDowns
)])}
265 {TSDowns
, TSLeechs
} = {lists:sublist(SDowns
, DUP3
),
266 lists:sublist(SLeechs
, SUP3
)},
267 sets:union(sets:from_list(TSDowns
), sets:from_list(TSLeechs
)).
269 rechoke_unchoke([], _PS
) ->
271 rechoke_unchoke([P
| Next
], PSet
) ->
272 case sets:is_element(P
, PSet
) of
274 etorrent_t_peer_recv:unchoke(P#rechoke_info
.pid),
275 rechoke_unchoke(Next
, PSet
);
277 [P
| rechoke_unchoke(Next
, PSet
)]
281 MinUp
= case application:get_env(etorrent
, min_uploads
) of
285 lists:max([MinUp
, upload_slots() - sets:size(PSet
)]).
287 rechoke_choke([], _Count
, _Optimistics
) ->
289 rechoke_choke([P
| Next
], Count
, Optimistics
) when Count
>= Optimistics
->
290 etorrent_t_peer_recv:choke(P#rechoke_info
.pid),
291 rechoke_choke(Next
, Count
, Optimistics
);
292 rechoke_choke([P
| Next
], Count
, Optimistics
) ->
293 case P#rechoke_info
.kind
=:= seeding
of
295 etorrent_t_peer_recv:choke(P#rechoke_info
.pid),
296 rechoke_choke(Next
, Count
, Optimistics
);
298 etorrent_t_peer_recv:unchoke(P#rechoke_info
.pid),
299 case P#rechoke_info
.r_interest_state
=:= interested
of
301 rechoke_choke(Next
, Count
+1, Optimistics
);
303 rechoke_choke(Next
, Count
, Optimistics
)
307 split_preferred_peers([], Downs
, Leechs
) ->
309 split_preferred_peers([P
| Next
], Downs
, Leechs
) ->
310 case P#rechoke_info
.kind
=:= seeding
311 orelse P#rechoke_info
.r_interest_state
=:= not_interested
of
313 split_preferred_peers(Next
, Downs
, Leechs
);
314 false
when P#rechoke_info
.state
=:= seeding
->
315 split_preferred_peers(Next
, Downs
, [P
| Leechs
]);
316 false
when P#rechoke_info
.snubbed
=:= true
->
317 split_preferred_peers(Next
, Downs
, Leechs
);
319 split_preferred_peers(Next
, [P
| Downs
], Leechs
)
322 seeding_torrents() ->
323 {atomic
, Torrents
} = etorrent_torrent:all(),
324 [T#torrent
.id
|| T
<- Torrents
,
325 T#torrent
.state
=:= seeding
].