MLDonkey Forum Index
Homepage •  Bugs •  Tasks •  Patches •  SF.net Project Page •  ChangeLog •  German forum •  Links •  Wiki •  Downloads
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 
 
Dev tutorials (BT conn. control + Emacs/Ocamldoc)

 
Post new topic   Reply to topic    MLDonkey Forum Index -> Development
View previous topic :: View next topic  
Author Message
HighTime
user


Joined: 05 Nov 2002
Posts: 242
Location: France

PostPosted: Tue Nov 04, 2003 4:38 pm    Post subject: Dev tutorials (BT conn. control + Emacs/Ocamldoc) Reply with quote

Tutorial : Connection Control in Bittorrent plugin

Problem :
Some users noticed that number of connections while using
bittorrent plugin is superior to max_opened_connections.

Knowledge :
Ocaml : basic
Mldonkey : basic
Network programming : basic


Let's start by doing a fresh cvs checkout :
Code:

>  cvs -d:pserver:anoncvs@subversions.gnu.org:/cvsroot/mldonkey login
Logging in to :pserver:anoncvs@subversions.gnu.org:2401/cvsroot/mldonkey
CVS password:  <enter>
> cvs -z3 -d:pserver:anoncvs@subversions.gnu.org:/cvsroot/mldonkey co
mldonkey
cvs server: Updating mldonkey
(...)


Now we can start working!

First thing to do is to find how is defined max_opened_connections.
Code:

> cd mldonkey
> cd src
> grep max_opened_connections **/*.ml
(I use zsh so **/*.ml means that it will search in .ml files of all subdirs)
daemon/common/commonGlobals.ml:  let max = mini !!max_opened_connections MlUnix.max_sockets in
daemon/common/commonOptions.ml:let max_opened_connections = define_option downloads_ini
daemon/common/commonOptions.ml:    ["max_opened_connections"] "Maximal number of opened connections"
daemon/common/commonOptions.ml:    "Bandwidth", "Maximal Number of Sockets Used", shortname max_opened_connections, "T";
daemon/driver/driverCommands.ml: strings_of_option_html max_opened_connections;


The last one is not interesting : we don't mess with html.
So let's have a look to daemon/common/commonOptions.ml

Code:

> less daemon/common/commonOptions.ml
 < /max_opened_connections >  (search for max_opened_connections in
the file )
first result : 
let max_opened_connections = define_option downloads_ini
    ["max_opened_connections"] "Maximal number of opened connections"
  int_option (min MlUnix.max_sockets 200)
second result :
   "Bandwidth", "Maximal Number of Sockets Used", shortname
   max_opened_connections, "T";
<q>  (to quit less)


With our basic ocaml skills we can't do anything with that. So
let's have a look to daemon/common/commonGlobals.ml :
Code:

> less daemon/common/commonGlobals.ml
< /max_opened_connections > (search for max_opened_connections in
the file )
let can_open_connection () =
  let ns = nb_sockets () in
  let max = mini !!max_opened_connections MlUnix.max_sockets in
  (*
  if !!debug_net then begin
      lprintf "CAN OPEN (conns: %d < %d && upload U/D: %d %d)\n" ns max
        (UdpSocket.remaining_bytes udp_write_controler)
        (UdpSocket.remaining_bytes udp_read_controler);
    end; *)
  ns < max
< q >


Ok now it's time to use our basic ocaml skills :
we have :

let can_open_connection () =
declaration of a function can_open_connection with one argument ()
here the arg is () which is of type unit (it's a little like no argument)

let ns = nb_sockets () in
local declaration of ns : the identifier ns will have a meaning
only in the code after in

(* a comment *)

We are lucky! The function have a significative name! Now let see
if it's used and how! We know connection control works in edonkey
plugin so we restrict search to this plugin :

Code:

> grep networks/donkey/*.ml
networks/donkey/donkeyClient.ml:  if can_open_connection () then begin
networks/donkey/donkeyClient.ml:                if can_open_connection () then
networks/donkey/donkeyServers.ml:  if can_open_connection () then
networks/donkey/donkeyServers.ml:  if can_open_connection () then
networks/donkey/donkeyServers.ml:      if n > 0 && can_open_connection () then begin
networks/donkey/donkeySources1.ml:    if CommonGlobals.can_open_connection () && index < nqueues then begin
networks/donkey/donkeySources1.ml:    if  CommonGlobals.can_open_connection () &&  nclients > 0 then
networks/donkey/donkeySources2.ml:    if CommonGlobals.can_open_connection () && nclients > 0 then begin
networks/donkey/donkeySources2.ml:    if CommonGlobals.can_open_connection () && nclients > 0 then begin
networks/donkey/donkeySources3.ml:    if CommonGlobals.can_open_connection () && n > 0 then
networks/donkey/donkeySources3.ml:    if  CommonGlobals.can_open_connection () && n > 0 then
networks/donkey/donkeySupernode.ml:      if can_open_connection () then



The first line is interesting :
testing if we can open a connection indicate that we will open a
connection after this line.

Code:

> less networks/donkey/donkeyClient.ml
< /can_open_connection >
if can_open_connection () then begin

...
 let sock = TcpBufferedSocket.connect "donkey to client" (
                  Ip.to_inet_addr ip)
                port
                  (client_handler c) (*client_msg_to_string*) in
...
end


What we found is hard to understand. Fortunately
naming of function is good in mldonkey so we found a
let sock = TcpBufferedSocket.connect
Here you need your basic network programming knowledge. You
probably know connect (man connect), listen. So here we
created a TcpBufferedSocket sock doing a connect (if we
can_open_connection ()!)
Search for something like this in bt plugin :

Code:

> grep TcpBufferedSocket.connect networks/bittorrent/*.ml
(nothing! too bad. Let see what we can find about TcpBufferedSocket)
> grep TcpBufferedSocket networks/bittorrent/*.ml
networks/bittorrent/bTClients.ml:open TcpBufferedSocket
networks/bittorrent/bTClients.ml:          let c = new_client file peer_id (TcpBufferedSocket.host sock) in
networks/bittorrent/bTClients.ml:              let ccc = new_client file peer_id (TcpBufferedSocket.host sock) in
networks/bittorrent/bTClients.ml:              TcpBufferedSocket.set_read_controler sock download_control;
networks/bittorrent/bTClients.ml:              TcpBufferedSocket.set_write_controler sock upload_control;
networks/bittorrent/bTClients.ml:              TcpBufferedSocket.set_rtimeout sock 30.;
networks/bittorrent/bTClients.ml:              let sock = TcpBufferedSocket.create
networks/bittorrent/bTClients.ml:              TcpBufferedSocket.set_read_controler sock download_control;
networks/bittorrent/bTClients.ml:              TcpBufferedSocket.set_write_controler sock upload_control;
networks/bittorrent/bTClients.ml:              TcpBufferedSocket.set_closer sock (fun _ r ->
networks/bittorrent/bTProtocol.ml:open TcpBufferedSocket
networks/bittorrent/bTProtocol.ml:  BTHeader of (gconn -> TcpBufferedSocket.t ->
networks/bittorrent/bTProtocol.ml:| Reader of (gconn -> TcpBufferedSocket.t -> unit)
networks/bittorrent/bTProtocol.ml:    mutable gconn_refill : (TcpBufferedSocket.t -> unit) list;
networks/bittorrent/bTProtocol.ml:      let b = TcpBufferedSocket.buf sock in
networks/bittorrent/bTProtocol.ml:              TcpBufferedSocket.buf_used sock 4;
networks/bittorrent/bTProtocol.ml:              TcpBufferedSocket.buf_used sock msg_len;
networks/bittorrent/bTProtocol.ml:    let b = TcpBufferedSocket.buf sock in
networks/bittorrent/bTProtocol.ml:            TcpBufferedSocket.buf_used sock (slen+49);
networks/bittorrent/bTProtocol.ml:            if not (TcpBufferedSocket.closed sock) then
networks/bittorrent/bTProtocol.ml:  TcpBufferedSocket.set_reader sock (handlers info gconn);
networks/bittorrent/bTProtocol.ml:  TcpBufferedSocket.set_refill sock (fun sock ->
networks/bittorrent/bTProtocol.ml:  TcpBufferedSocket.set_handler sock TcpBufferedSocket.WRITE_DONE (
networks/bittorrent/bTProtocol.ml:(*                TcpBufferedSocket.close sock "write done" *)


Once again correct naming saves our day :
let sock = TcpBufferedSocket.create
Do you remember how we declare a variable/function/... ?
let v = ...
Ok it seems that we created a socket with this line

Code:

> less networks/bittorrent/bTClients.ml
< /TcpBufferedSocket.create >

let listen () =
  try
    let s = TcpServerSocket.create "bittorrent client server"
        Unix.inet_addr_any
        !!client_port
        (fun sock event ->
          match event with
            TcpServerSocket.CONNECTION (s,
              Unix.ADDR_INET(from_ip, from_port)) ->
              lprintf "CONNECTION RECEIVED FROM %s\n"
                (Ip.to_string (Ip.of_inet_addr from_ip))
              ;
             
             
 -->        let sock = TcpBufferedSocket.create
                  "bittorrent client connection" s
                  (fun sock event ->
                    match event with
                      BASIC_EVENT (RTIMEOUT|LTIMEOUT) ->
                        close sock Closed_for_timeout
                    | _ -> ()
                )
              in
              TcpBufferedSocket.set_read_controler sock download_control;
              TcpBufferedSocket.set_write_controler sock upload_control;
             
              let c = ref None in
              TcpBufferedSocket.set_closer sock (fun _ r ->
                  match !c with
                    Some c ->  begin
                        match c.client_sock with
                        | Connection s when s == sock ->
                            disconnect_client c r
                        | _ -> ()
                      end
                  | None -> ()
              );
              set_rtimeout sock 30.;
              incr counter;
              set_bt_sock sock !verbose_msg_clients
                (BTHeader (client_parse_header !counter c false));
          | _ -> ()
      ) in
    listen_sock := Some s;



Interesting : we have something which is called listen.
With your basic networking skills you know what it is :
the server socket used by incoming clients to connect to us.
Code:

        (fun sock event ->
          match event with
            TcpServerSocket.CONNECTION (s,
              Unix.ADDR_INET(from_ip, from_port)) ->

We 'bind' a function with the listening socket :
this function will associate event with some work.
You should have a look to the match ... with thing in an ocaml
tutorial but here's how we could do something similar in C

switch (event) {
case CONNECTION :
...
break;
default :
...
}

The server socket create a new socket used to work with incoming peer
on a CONNECTION event.

Ok here it require a little more than basic ocaml skills. So i'll show
you :

Code:

             if can_open_connection () then
      begin
              let sock = TcpBufferedSocket.create
         ...
              set_bt_sock sock !verbose_msg_clients
                (BTHeader (client_parse_header !counter c false));
      end
              else
      (*don't forget to close the incoming sock if we can't
      open a new connection*)
      Unix.close s


We test if we can_open_connection : if we can, do it
else close the incoming sock (not the listening one).
(I saw it's how it is done in donkeyClients.ml ...)

Let's compile to see if it's ok
Code:

> cd ..
> ./configure --(your favorite settings) && make
...


It's ok. While coding your patch you will probably have
to know the flow of an ocaml program to compile correctly.
Know you can test your patch... and soon you will discover that it
don't work.
Why? Because we only control incoming connections, we forgot outgoing!
You have to know how bittorrent works :
you download a .torrent file. In this file there's information
of what you will download. Plus there is the address of a tracker.
A tracker in bt is something that 'glue' all clients by sending
a list of known peers. So those peers will be outgoing connections.

Code:

> cd src/networks/bittorrent
> grep track *.ml
(some output you'll have to check to find something interesting
bTInteractive.ml:  BTClients.connect_tracker file !announce;
 for example)


In this line we call a function connect_tracker. This function
is in bTClients.ml

Code:

> less bTClients.ml
< /connect_tracker >
(...)
                         let c = new_client file !peer_id (!peer_ip,!port)
(...)
      resume_clients file


Something called new_client! You can find where it's defined
by doing a
Code:

> grep let\ new_client *.ml


but you won't find something which create a socket.
So you're back to connect_tracker. The last thing this function do
is "resume_clients". As usual ...

Code:

> grep let\ resume_clients *.ml
bTClients.ml:let resume_clients file =
> less bTClients.ml
< /resume_clients >
  Hashtbl.iter (fun _ c ->
      try
        match c.client_sock with
        | Connection sock ->
            lprintf "RESUME: Client is already conencted\n";
            get_from_client sock c
        | _ ->
            (try get_file_from_source c file with _ -> ())
      with e -> ()
(* lprintf "Exception %s in resume_clients\n"   (Printexc2.to_string e) *)
  ) file.file_clients


There's a comment saying that in this case the 'Client is already
conencted'.
So we fall in the other case (no connection) :
'get_file_from_source c file'

Code:

> grep let\ get_file_from_source *.ml
bTClients.ml:let get_file_from_source c file =
> less bTClients.ml
< /get_file_from_source >
 if connection_can_try c.client_connection_control then begin
      connect_client c
    end else begin
      print_control c.client_connection_control
    end


Ok here you can find what connect_client do or simply prevent
this function to be called :

Code:

let get_file_from_source c file =
  if (connection_can_try c.client_connection_control) && [b] can_open_connection () [/b] then begin
      connect_client c
    end else begin
      print_control c.client_connection_control
    end 


compile and test your patch and... it doesn't work.
I'm a bit unlucky. I thought it was an easy fix, but it turned
out to be a little tricky. I won't explain in this tutorial what
to do to make this patch correct, but if you are interested
you can have a look at the [url=http://savannah.nongnu.org/patch/index.php?func=detailpatch&patch_id=2247&group_id=1409]
final patch. [/url]

Make your patch :
Code:

> make clean
> cvs diff -u -w > name.patch
...


Conclusion :
At least for the first part it was not a difficult work :
with little skills and a lot of good sense you can contribute
to your favorite p2p client, fixing little glitches here and there.



Tip :
use a good syntax colorizer :
Tuareg for emacs


Last edited by HighTime on Wed Nov 05, 2003 2:21 pm; edited 4 times in total
Back to top
View user's profile Send private message
HighTime
user


Joined: 05 Nov 2002
Posts: 242
Location: France

PostPosted: Tue Nov 04, 2003 4:45 pm    Post subject: Reply with quote

Stay tuned for another (hopefully easier) tutorial.
Back to top
View user's profile Send private message
pango
Sage


Joined: 31 Oct 2002
Posts: 2436

PostPosted: Wed Nov 05, 2003 1:26 am    Post subject: Reply with quote

Good idea, I like it!
While grep give interesting results, Emacs tags, or ocamlbrowser can be useful too (I use Emacs tags more often). If someone's interested, I'll write something about their use, it's not that difficult.
Back to top
View user's profile Send private message
HighTime
user


Joined: 05 Nov 2002
Posts: 242
Location: France

PostPosted: Wed Nov 05, 2003 1:30 pm    Post subject: Reply with quote

Every tutorial is welcome,
especially when it's about being more productive with emacs and ocaml.
Back to top
View user's profile Send private message
dvj
neophyte


Joined: 08 May 2003
Posts: 7
Location: Norway

PostPosted: Wed Nov 05, 2003 1:37 pm    Post subject: Reply with quote

how about putting that into the wiki? great tortural by the way Smile
Back to top
View user's profile Send private message Yahoo Messenger
pango
Sage


Joined: 31 Oct 2002
Posts: 2436

PostPosted: Wed Nov 05, 2003 2:01 pm    Post subject: Reply with quote

Ok, when you have to manage many sources files, you can generate an index of all declarations, called "TAGS", which can then be used for fast navigation.
The default tool to do that is called "etags", and it knows how to parse many languages, C, C++, Java, Perl, Cobol, Fortran, Lisp, etc (see etags --help for complete list). Just do

Code:

$ etags **/*.[ch]

or, if you don't use zsh,
Code:

$ etags `find . -name "*.[ch]"`

... and you have a TAGS index of all C sources in your tree Smile

Problem is, current version of etags doesn't support ocaml Sad
So you have to use "otags" (taken from http://perso.rd.francetelecom.fr/alvarado/) instead:
Code:

$ otags -r .


If you want to index both ocaml and C sources, you can use
Code:

$ otags -r .
$ etags -a **/*.[ch]

(-a like "add")

And now, what ?
In Emacs, you can use that index to find some declaration; Type M-. (ESC . usually), then some variable or function identifier. You can even use <tab> completion! Press enter and Emacs will open the file that contains its declaration, with the cursor at the right place (the first time, it will ask for the TAGS file path).

How to search all occurences of something ? Type M-x tags-search, enter a string, and Emacs will display the first occurence of that string. Type M-, and it will show next one, etc.

You can also substitute string with M-x tags-query-replace...


Last edited by pango on Wed Nov 05, 2003 2:19 pm; edited 1 time in total
Back to top
View user's profile Send private message
pango
Sage


Joined: 31 Oct 2002
Posts: 2436

PostPosted: Wed Nov 05, 2003 2:15 pm    Post subject: Reply with quote

ocamlbrowser is another graphical (tk ?) tool for doing identifiers lookups. To use it, you must first compile your sources, because it uses ocaml object files to do its work. Then run

Code:

$ ocamlbrowser -I path -I path...


were paths are directories where modules can be found. If you want to add all directories,

Code:

$ ocamlbrowser `for i in **/*.cm?; do dirname $i; done|sort -u|sed -e 's/^/ -I /'`


should do the trick Wink

Once started, you can list modules declarations, search specific identifiers, etc. What's great, is that it shows the ocaml types of identifiers (which is sometimes god sent Wink )...
Back to top
View user's profile Send private message
HighTime
user


Joined: 05 Nov 2002
Posts: 242
Location: France

PostPosted: Wed Nov 05, 2003 2:15 pm    Post subject: Reply with quote

dvj wrote:
how about putting that into the wiki? great tortural by the way Smile

I hope that there will be a lot of questions/interaction so i think wiki is not perfect for this.
Thanks Pango for the tutorial!
Back to top
View user's profile Send private message
HighTime
user


Joined: 05 Nov 2002
Posts: 242
Location: France

PostPosted: Wed Nov 05, 2003 8:05 pm    Post subject: Reply with quote

Tutorial : Drop BT clients after two unsuccesfull connection

Problem :
Some users reported that memory usage grow while using BT plugin.
The idea is to really drop clients after we failed to connect
to them 2 times.

Knowledge :
Ocaml : basic
Mldonkey : basic
Networking : low


First here's the sources layout of mldonkey (as explained in
mldonkey/docs/developers/sources_tree.txt):

Code:

In src/:

daemon/      Sources of MLdonkey daemon
                 common/: common types used by all network plugins
                 driver/: drivers (main, interfaces)
networks/    Sources of File-Sharing Plugins
utils/       Sources of useful modules
                 cdk/: general modules
                 lib/: general modules specialized for MLdonkey
                 net/: scheduler for events (sockets)


In this tutorial we will put our dirty little fingers in
networks/bittorrent/ and utils/net/basicSocket.ml

Let's have a look inside networks/bittorrent/
(BT plugin is great to begin with because it's very clean
compared to edonkey plugin)

Code:

> ls
bencode.ml    bTComplexOptions.ml  bTInteractive.ml  bTOptions.ml   bTTypes.ml
bTClients.ml  bTGlobals.ml         bTMain.ml         bTProtocol.ml 



    bencode.ml : describe is a module used to decode/encode bt
    dictionnaries (used between client <-> tracker)
    bTMain.ml : main file of plugin (intialize plugin)
    bTInteractive.ml : input/output functions of bt plugin (load_torrent,
    cancel ...)
    bTOptions.ml : define bt plugin options (client_port,prefix,...)
    bTComplexOptions.ml : save/load structures use in plugin to/from
    bittorrent.ini
    bTTypes.ml : definion of types used in this plugin
    bTGlobals.ml : glue plugin-internal functions to common/ functions
    bTProtocol.ml : parse/write bt protocol ()
    bTClients.ml : client<->client work


We will take a little shortcut for this tutorial : i'll show
what will be done when we can't connect. When creating a
tcpBufferedSocket we do something like this :

Code:

    | BASIC_EVENT (CLOSED r) ->
       begin
      match c.client_sock with
        | Connection s when s == sock ->
             disconnect_client c r
        | _ -> ()
     end;   


Do you remember how a function matching CONNECTION
event was 'bound' to the listening socket in the
"Connection Control in Bittorrent plugin" tutorial?
Here we have the same thing except that the function is matching
'CLOSED' events.
So when this socket will receive a CLOSED event we will
call the disconnect_client function (with the client and r as
arguments).
But first, do you see this 'BASIC_EVENT (CLOSED r)' ?
It's a 'parameterized' type. The definition of this kind of type is
'BASIC_EVENT of something'. You can search the definition inside
mldonkey source and you will find it in
utils/net/tcpBufferedSocket.ml

Code:

type event =
  WRITE_DONE
| CAN_REFILL
| CONNECTED
| BUFFER_OVERFLOW
| READ_DONE of int
| BASIC_EVENT of BasicSocket.event


Now let's see what is the definition of a BasicSocket.event
(it's in the module BasicSocket which is the file basicSocket.ml
in utils/net/)

Code:

type event =
| CLOSED of close_reason
(...)


Ok, so the type of the argument r passed to disconnect_function is
close_reason (in basicSocket.ml)

Code:

type close_reason =
    Closed_for_timeout    (* timeout exceeded *)
  | Closed_for_lifetime   (* lifetime exceeded *)
  | Closed_by_peer       (* end of file *)
  | Closed_for_error of string
  | Closed_by_user
  | Closed_for_overflow
  | Closed_connect_failed
  | Closed_for_exception of exn


Ok now we have found how to detect a failed connection :
we will have to do something special when the reason of closing
is 'Closed_connect_failed'.
Let's get back to the function called on a 'CLOSED' event :
disconnect_client c r (it's in networks/bittorrent/bTClients.ml)

Code:
 
let disconnect_client c reason =
  if !verbose_msg_clients then
    lprintf "CLIENT %d: disconnected\n" (client_num c);
  match c.client_sock with
    NoConnection | ConnectionWaiting | ConnectionAborted -> ()
  | Connection sock ->
      close sock reason;
      try
        List.iter (fun r -> Int64Swarmer.free_range r) c.client_ranges;
        c.client_ranges <- [];
        c.client_block <- None;
        if not c.client_good then
          connection_failed c.client_connection_control;
        c.client_good <- false;
        set_client_disconnected c reason;
        (try close sock reason with _ -> ());
        c.client_sock <- NoConnection;
        let file = c.client_file in
        c.client_chunks <- [];
        c.client_allowed_to_write <- zero;
        c.client_new_chunks <- [];
        Int64Swarmer.unregister_uploader_bitmap
          file.file_partition c.client_bitmap;
        for i = 0 to String.length c.client_bitmap - 1 do
          c.client_bitmap.[0] <- '0';
        done
      with _ -> ()


Now it's time to work :
we will have to check what is r :

Code:

match reason with ...     


if it's a Closed_connect_failed let's do something :

Code:

match reason with
 | Closed_connect_failed -> (* do something *)


if it's not (aka something else) do nothing :

Code:

match reason with
 | Closed_connect_failed -> (* do something *)
 | _ -> ()



You already know that you don't need to worry about memory when coding
ocaml : you never do malloc and free. So how to 'free' a client?
Garbage Collector free an object when an object is not
referenced by any other object. So we will have to
remove any reference to a client and wait the GC to do its job.

Where are clients referenced? A client is a file-centric structure :
so clients are referenced inside the file structure (in bTTypes.ml):

Code:

and file = {
    file_file : file CommonFile.file_impl;
    file_piece_size : int64;
    file_tracker : string;
    file_id : Sha1.t;
    file_name : string;
    file_swarmer : Int64Swarmer.t;
    file_partition : CommonSwarming.Int64Swarmer.partition;
    mutable file_clients : (Sha1.t, client) Hashtbl.t ;
    mutable file_chunks : Sha1.t array;
    mutable file_tracker_connected : bool;
    mutable file_tracker_interval : int;
    mutable file_tracker_last_conn : int;
    mutable file_files : (string * int64 * int64) list;
    mutable file_blocks_downloaded : Int64Swarmer.block list;
  }   


We know where are referenced clients : inside file_clients.

Code:

    mutable file_clients : (Sha1.t, client) Hashtbl.t ;


In this piece of code we see a declaration of a member of the file
structure. The 'mutable' keyword indicate that the member can change
during the life of the file (file_id cannot change).
'file_clients' is the name of the member and
'(Sha1.t, client) Hashtbl.t' is the type of the member.
What's this type? It's an HashTable which associates keywords of type
'Sha1.t' with values of type 'client'.
You can check ocaml doc to see how to use a Hashtbl.

In the definition of new_client in bTGlobals.ml we can see :

Code:

      Hashtbl.add file.file_clients peer_id c;
      file_add_source (as_file file.file_file) (as_client c);



Here we add the newly created client to the file_clients member of the
file shared by this client.
The second line is interesting also : i could have missed another
reference to this client.
Now to remove references to a client we have to do the opposite of this
two lines. Let's create a remove_client function (in bTGlobals.ml):

Code:

let remove_client c =
    Hashtbl.remove c.client_file.file_clients c.client_uid ;
    file_remove_source (as_file c.client_file.file_file) (as_client c)



You can learn more of what's in the 'client' structure but here is how
it works :

Code:

        |___Client__ |            |____file____|
        |client_file  |-------> |                    |
        |                   |<---1---|file_clients |
        |                   |             |file_file       |
        |___________|             --------------
                î                              |
                |                             v
                |                    |_file_impl___|
                \---------2-----|____________|


With first line of remove_client we remove the first reference
and with the second line, the second reference (file_remove_source
is the opposite of file_add_source and is defined at the same place).

We'll have to use this new function on third failed connection.
So we must introduce a little counter associated with each
client (in type client in bTTypes.ml).

Code:

   (...)
    mutable client_good : bool;
    mutable client_num_try : int;
    }


It's mutable because we will increase it for each failed connection.
We need to initialize this new counter in the new_client function
of bTGlobals.ml

Code:

   (...)
    client_new_chunks = [];
    client_good = false;
    client_num_try = 0;
    } and impl = {
        (...)


Time to go back to our disconnect_client function :

Code:

match reason with
 | Closed_connect_failed -> (* do something *)
 | _ -> ()


Now we can do something :

Code:

match reason with
 | Closed_connect_failed -> if c.client_num_try = 2 then   
               remove_client c
             else      
               c.client_num_try <- c.client_num_try+1;
 | _ -> ()


We put this piece of code at the end of disconnect_client :

Code:

let disconnect_client c reason =
  if !verbose_msg_clients then
    lprintf "CLIENT %d: disconnected\n" (client_num c);
  begin
  match c.client_sock with
    NoConnection | ConnectionWaiting | ConnectionAborted -> ()
  | Connection sock ->
      close sock reason;
      try
        List.iter (fun r -> Int64Swarmer.free_range r) c.client_ranges;
        c.client_ranges <- [];
        c.client_block <- None;
        if not c.client_good then
          connection_failed c.client_connection_control;
        c.client_good <- false;
        set_client_disconnected c reason;
        (try close sock reason with _ -> ());
        c.client_sock <- NoConnection;
        let file = c.client_file in
        c.client_chunks <- [];
        c.client_allowed_to_write <- zero;
        c.client_new_chunks <- [];
        Int64Swarmer.unregister_uploader_bitmap
          file.file_partition c.client_bitmap;
        for i = 0 to String.length c.client_bitmap - 1 do
          c.client_bitmap.[0] <- '0';
        done
      with _ -> ()
  end;
  match reason with
    | Closed_connect_failed ->
   if c.client_num_try = 2 then   
     remove_client c
   else      
     c.client_num_try <- c.client_num_try+1
    | _ -> ()


Conclusion :
I'm not an theory of languages expert so i cannot guarantee that
those clients will be freed (but i think it's ok).
There's still too much client doing nothing : we will have to do
more checking in future.

While reading bTClients.ml i found a little bug :
we sometime 'leak' a client. I let the reader find this bug
as an exercise.
Hint : it's near the creation of a new client.
The solution is in the patch.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    MLDonkey Forum Index -> Development All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Sourceforge.net Logo