25
Apr
2007

Asynchronous calls explained

Someone emailed me recently to ask about a case of asynchronous calls that I think is fairly common. At the risk of opening up the floodgates (no, you cannot email me every time you have a Flex question!!!), I think it’s worth answering publicly because it probably trips a lot of people up.

Note that this is a more basic (and common?) case than the stuff I was talking about earlier. For more advanced solutions, see here, here and here.

From: XXXX@XXXXXXXXX

I have two loops. One grabs an Xml file that contains a list of a bunch of Xml files I use to layout data to print. (What I am doing is printing a set of documents).

So, as I loop through each, I call the URLRequest to grab the Xml to process and register the Complete event (which takes care of processing the Xml).

So if I have 20 documents, I find only the last one gets handled, the rest are ignored. I am guessing this is because the thread does not stop. I am thinking synchronous events are not an option either.

What I want to happen is one fires, it lets me do what I want to do, then the next one fires. Or alternatively, one is processed, then the next one is processed.

Am I making sense?

Yes, it does make sense, and I bet a lot of people get stuck on this. So here’s the answer.

~

The problem

Let’s take a look at the following (simplified version) of the code that was sent to me:

var loader : URLLoader = new URLLoader();
public function loadItAll(tree: XML)
{
    for each (var elem:XML in tree.elements())
    {
        loader.load(new URLRequest(elem.text().toString()));
        loader.addEventListener(Event.COMPLETE, onComplete);
    }
}

public function onComplete(event: Event)
{
    myXMLResult = new XML( loader.data );
    // handle the URL request here
}

The expectation here was that onComplete() would be called n times, one for each URL request. As it turned out, onComplete() was only called once.

So what’s happening?

Flash network calls are asynchronous, which means that the loader.load() call doesn’t happen right away. In fact, all the code in the for loop runs before any of the network calls happen.

To understand the order of events, it might be easier to use a physical analogy, like asking a valet to go get a car. (It’s easier for me, anyway. I think it’s because of the way human brains have evolved — it’s a lot easier for me to think about physical things like people and vehicles, not network calls and data packets!)

Imagine the following pseudocode.

public function doItAll()
{
    for each (person in line)
    {
        ask the valet to go get the person's car.
        ask the valet to say "car ready" when the car is ready. 
    }
}

public function carReady(car: Car)
{
    get in the car and drive home.
}

Remember that all of the ActionScript code has to finish running before the asynchronous methods start executing. In the above psuedocode, the valet takes all the requests for all the people in line before going to get a single car.

As it turns out, this valet is only capable of returning one car at a time (not surprising) and more importantly, can only remember one request at a time. Ack! So when we went through the line of people and asked each one of them for their ticket, he only remembered the last request! This is the reason that only one car is returned.

There are a few ways to fix this problem:
1) Hire lots of valets (one for each person in line).
2) Don’t ask for the next car until the first car is back.
3) Teach the valet to remember multiple requests.

Let’s stop our analogy right here and get back to our original psuedocode for each of these three strategies.

Strategy 1: Hire lots of valets

This is probably the simplest strategy, but it doesn’t work in all cases. Here is the pseudocode:

public function loadItAll(tree: XML)
{
    for each (var elem:XML in tree.elements())
    {
        var loader : URLLoader = new URLLoader();
        loader.load(new URLRequest(elem.text().toString()));
        loader.addEventListener(Event.COMPLETE, onComplete);
    }
}

public function onComplete(event: Event)
{
    var loader = URLLoader(event.target);
    loader.removeEventListener(Event.COMPLETE, OnComplete);

    myXMLResult = new XML( loader.data );
    // handle the URL request here
}

Notice that every time through the loop, we are creating a new URLLoader. Because of this, we have to make sure we are referring to the correct URLLoader during the onComplete() method. We do this by getting the URLLoader from the target of the complete event that was fired.

It’s probably also a good idea to remove the event listener to avoid having lots and lots of URLLoader objects created without being destroyed.

This strategy will not work if you need to have items loaded in a certain order, or if spawning lots and lots of URLLoaders will cause a performance problem. (In this case, I think this is throttled by the network stack, so I think you are OK. In other cases, it might not be OK).

Strategy 2: Don’t ask for the next car until the first car is back

This is probably the next simplest strategy.

var loader : URLLoader = new URLLoader();
var list : XMLList;
var i : int;

public function loadItAll(tree: XML)
{
    loader.addEventListener(Event.COMPLETE, onComplete);
    list = tree.elements();
    i = 0;
    loadNext();
}

public function loadNext()
{
    if (i < list.length())
    {
        var elem: XML = list[i++];
        loader.load(new URLRequest(elem.text().toString()));
    }
}

public function onComplete(event: Event)
{
    myXMLResult = new XML( loader.data );
    // handle the URL request here

    loadNext();
}

The cons of this method are that none of the network operations are happening in parallel, which can be a performance problem. Also, the logic to walk through the list of entires is scattered throughout multiple methods. You can clean this up using state objects or other strategies, but you quickly get to strategy three, which is:

Strategy 3: Teach the valet to remember multiple requests

This is the most work, and you can make this as complex as you like. The benefits are that it centralizes the handling of this loop into a single place.

class MyLoader extends URLLoader()
{
   override public function load(string: URL)
   {
       // put stuff on a queue
       // if there are no pending requests, call loadNext();
   }

   private function loadNext()
   {
       // remove one thing from the queue and load it
   }

   private function onComplete()
   {
       // dispatch the COMPLETE event
       // initiate the next load
   }
}
var loader : MyLoader = new MyLoader();
public function loadItAll(tree: XML)
{
    loader.addEventListener(Event.COMPLETE, onComplete);

    for each (var elem:XML in tree.elements())
    {
        loader.load(new URLRequest(elem.text().toString()));
    }
}

public function onComplete(event: Event)
{
    myXMLResult = new XML( loader.data );
    // handle the URL request here
}

There are a number of things to worry about here, such as whether it is OK to be returning data in loader.data, given that the same object is being reused for multiple requests. Probably the right thing to do is to create a token object (using the AsyncToken class?) that holds all the information and to pass that to the user. And then you can start building callback mechanisms into that AsyncToken. And then... well, it gets more and more complicated.

Details of some more advanced solutions around this can be found here, here and here.

30 Responses to “Asynchronous calls explained”

  1. liquidgod

    Sho, first thanks for taking this question. Its funny, I actually tried variants of all three of these, but none of them worked. Not because what you suggest does not work, but my implementation for each was just slightly off. I am going to go try these now and if I get it working I will get it back to you and explain what I did.

  2. sho

    Definitely let me know how things turn out. I didn’t actually try the code out.. I just wrote the sample code off the top of my head. There’s a chance that there is some subtlety that I hadn’t considered… *gulp*

  3. liquidgod

    It should be removeEventListener, not removeEventHandler. Right? I am using Flex Builder 2.0.1.

  4. Jeremyx

    I built an async function queue. It’s pretty flexible:

    http://jeremyx.tumblr.com/post/297637

  5. sho

    Thanks! I updated the code sample. LMK if you find other discrepancies.

  6. Ray Greenwell

    Sho, you wrote:

    It’s probably also a good idea to remove the event listener to avoid having lots and lots of URLLoader objects created without being destroyed.

    I think that this is incorrect.

    It would seem to me that when you add a listener to an EventDispatcher, the EventDispatcher has a reference to that listener Function, which has a reference to the enclosing class instance that contains the function. But if the EventDispatcher itself is not referenced anywhere else, then it should still be able to be garbage collected, correct?

    In other words, while it’s a good idea to always remove your listeners in general, for strategy #1 above it’s not strictly necessary because there are no other references to each loader. In fact, I would argue that your code is buggy, because there’s nothing to prevent the loaders from getting gc’d prior to them COMPLETEing. One should really push each loader into an array, and in the COMPLETE handler remove the reference from the array so that the loader can be garbage collected.

    See this post:
    http://jalava.blogspot.com/2007/02/fun-with-garbage-collection-and-loaders.html

    So in other words, I don’t think that adding a listener to an EventDispatcher will prevent that dispatcher from being gc’d. It does the other way around: the listener cannot be gc’d as long as there’s a reachable reference to the EventDispatcher somewhere, unless you add the listener “weakly” using the final argument of addEventListener.

  7. liquidgod

    I have gone through the first example. While yes, it works, it does not work for the particulars of my case. Here is why. While it does fire onComplete for each item I need to process, I need user input on each of those. One after the other. So while the event gets fired/handled 20 times; the effect is I can only process all of them at once, or none. I could see this working fine for someone who did not need manual intervention. So on to method 2/3.

  8. liquidgod

    I have a question about method 3, you state:

    // put stuff on a queue
    // if there are no pending requests, call loadNext();

    How do you tell that a request is pending? Or did you mean to say if there is anything the queue, pop the first one out? Basically I am just not sure if you mean is this a data structure thing, or some value we need to check. (Sorry, still trying to get my mind wrapped around the freewheeling nature of ActionScript.)

  9. liquidgod

    I am still having no luck with the third implementation. I get it to fire the handler, but the queue is empty on the subsequent calls. Can you make the example a bit more concrete? I see in your program that you create an instance of the loader, the loop, loading each item into the strucutre (in my case I wrote a queue). In the load function itself, I add items to the queue. How do you tell there are no items pending though? I tried using flags that were set inside loadnext and onComplete, but that did not work. Reason being the first item is popped out of the queue before the next is enqueued, and then when it comes around again, the queue is empty. I am sure I am missing something simiply on this.

    Additionally, in the onComplete, you say dispath the complete event. How is that done? If I already tied event.complete and its handler to the MyLoader object, won’t dispatching it again simply call the onComplete function again? Or are you saying to call the other onComplete where we utilize the result?

    Anyway, if you could give a more concrete example, that be great.

    Speaking of which, Jeremyx, got an example of yours somewhere?

  10. sho

    Jeremy: cool! Looks like a nice, general implementation. I imagine that it might also pay to have something more specific for a case you’d use often (like URLLoader).

    Ray: you are totally right. Ack! I was confusing myself with the other, fairly common case where a listener (not the dispatcher) is not garbage collected because it is still listening to events. This is the opposite of the case above.

    Liquidgod: I’ll post some more detailed pseudocode in a sec.

  11. sho

    Ray: I’ve been thinking about Jalava’s post, and I don’t know if it is correct. In other words, I do not believe that the URLLoaders above will get garbage collected.

    The loaders above are not visibly referenced in any code written above. However, I believe that once a load call is initiated, the system needs to hold a reference to the loader object somewhere. Otherwise, it has no way of returning the loader object back to the event handler once the load call completes.

    I’d have to dig into the source code to figure out where he reference is, but if these objects are actually being garbage collected, I would be surprised.

  12. sho

    Liquidgod: here is a simple implementation of strategy 3 to get you started. WARNING: This is, again, off the top of my head, and may not be correct. It is worth exactly what you are paying. :-)

    class MyLoader extends URLLoader()
    {
       var queue : Array = new Array();
       var pending : Boolean = false;
    
       public function MyLoader()
       {
           this.addEventListener(Event.COMPLETE, onComplete)
       }
    
       override public function load(request: URLRequest)
       {
           queue.push(request);
           loadNext();
       }
    
       private function loadNext()
       {
           if (!pending && queue.length() != 0)
           {
               var request : URLRequest = queue.shift() as URLRequest;
               super.load(request);
               pending = true;
           }
       }
    
       private function onComplete()
       {
           pending = false;
           loadNext();
       }
    }
    
  13. Ray Greenwell

    Sho: Wow.

    I swear I had done a test similar to what Jalava did in the past and discovered that the loaders could go away, but…

    I tried it with URLLoader and Loader, starting 16 of them, retaining no references, and then immediately had a loop that allocated a large amount of memory (to try to force a GC). Everything loaded fine.

    So: because the loader is using the network, the flash player has a reference to it behind the scenes? I can accept that. What about other objects? If I create a sprite, add a listener, and then forget about the sprite, will the fact that it has a listener prevent it from being garbage collected?

  14. Ray Greenwell

    Double wow.

    I just did a test allocating big blocks of memory and using System.totalMemory.
    It appears as if an EventDispatcher cannot be collected if there are ANY listeners registered with it. Can you root around internally there at Adobe and find someone who can confirm or deny this statement?

  15. sho

    Hi Ray.

    I’m not sure if it’s the player or the Flex framework that has a reference to the loader object. My guess is that it’s the Flex framework, but I wouldn’t put money on that.

    Either way, though, logic dictates that someone needs to be holding a reference to it, not because the player engineers are geniuses, and not because the framework engineers are geniuses, but because it’s inherent in the way garbage collection works.

    The whole idea behind garbage collection is that it is designed to identify objects that are incapable of having any impact on the running program. After reading Jalava’s post, I got nervous that there was a loophole for asynchronous operations. But if you think about it, there are really only two possible scenarios, assuming that garbage collection works as it should:

    1. After initiating the network call, the loader object is no longer needed at all, in which case it can be safely garbage collected, or
    2. The loader object is still “needed”, which is to say that someone somewhere is going to call methods on it or use the object in some way at some point in the future, which means that someone somewhere is holding a reference to it. Remember that even when we are talking about mysterious things like loaders getting called when a URLRequest completes, someone has to be waiting for the URLRequest completion (either the player or the framework) and calling the Loader when the request is complete, which means that someone has to be holding a reference.

    There are loopholes, of course, but I doubt that the case of the loader falls into any of these loopholes:

    1. Someone is incorrectly keeping a weak reference to the loader object. This is definitely possible, but since weak references were introduced relatively late in the development cycle for Flex 2, I am guessing that this is not the case.
    2. The C++ player code is holding a reference to the loader without properly telling the AVM to keep the reference alive. This is somewhat less likely than the first loophole for a number of reasons.
    3. This is sort of a sub-case of the above, but I could (almost) imagine a case where the reference to the object was being persisted outside the player (for example, as part of a data packet that was being sent along with the network call). In such a case, whoever created this crazy scheme (either the player or framework) would be responsible for maintaining a separate reference for the object within the player to prevent the object from being destroyed.

    So the bottom line is that it I am 95% sure that the loader can’t be garbage collected while it is active, but I’m not 100% sure.

    As for the EventDispatcher issue, I’ll ask around. I believe that objects with listeners can be collected, but I don’t know for sure. I can imagine some reasons why these objects would not be destroyed, but it sounds fishy to me.

    When you talked above about sprites, were you talking about sprites on the display list? If so, they should not be collected. If they are not on the display list, having listeners should not prevent them from being garbage collected (although I am not 100% sure on this point).

  16. trace(chris.foster) » kuwamoto.org » Blog Archive » Asynchronous calls explained

    […] kuwamoto.org » Blog Archive » Asynchronous calls explained April 26th, 2007 […]

  17. Nate Chatellier

    Great post Sho! Nicely explained, nice analogy, nice work.

  18. Ray Greenwell

    Sho,

    I’ve learned some more.

    Here’s my test program:

    package {
    
    import flash.display.Sprite;
    
    import flash.events.Event;
    import flash.events.MouseEvent;
    
    import flash.text.TextField;
    
    import flash.system.System;
    
    [SWF(width="500", height="500")]
    public class GarbageTest extends Sprite
    {
        public function GarbageTest ()
        {
            addEventListener(Event.ENTER_FRAME, handleFrame);
    
            _tf = new TextField();
            _tf.background = true;
            _tf.multiline = true;
            _tf.width = 500;
            _tf.height = 500;
            addChild(_tf);
    
            _removeListener = (Math.random() >= .5);
            _event = (Math.random() >= .5) ? Event.ENTER_FRAME : MouseEvent.CLICK;
            _tf.appendText("Will " + (_removeListener ? "" : "NOT ") +
                "remove '" + _event + "' listener...");
        }
    
        protected function handleFrame (... ignored) :void
        {
            _frameCount++;
            switch (_frameCount) {
            case 30:
                _tf.appendText("\n\nBefore creation: " + memUsage());
                _otherSprite = new Other();
                _otherSprite.addEventListener(_event, theBlackHole);
                break;
    
            case 31:
                _tf.appendText("\nCreated big sprite: " + memUsage());
                break;
    
            case 60:
                if (_removeListener) {
                    _otherSprite.removeEventListener(_event, theBlackHole);
                }
                _otherSprite = null;
                _tf.appendText("\nNulled sprite: " + memUsage());
                break;
    
            case 90:
                genCrap(new Array());
                _tf.appendText("\nGenerated more objects and immediately tossed: " +
                    memUsage());
                break;
    
            case 120:
                _tf.appendText("\nFinally... " + memUsage());
                break;
    
            case 150:
                _tf.appendText("\n\nReload to try again.");
                break;
            }
        }
    
        protected function memUsage () :String
        {
            return String(int(System.totalMemory / (1024 * 1024))) + "MB";
        }
    
        public function theBlackHole (... ignored) :void
        {
            trace("What are you doing in here?");
        }
    
        public static function genCrap (crap :Array) :void
        {
            for (var ii :int = 0; ii < 10000; ii++) {
                var s :String = "";
                for (var jj :int = 100; jj >= 0; jj--) {
                    s += String.fromCharCode(int(Math.random() * 26) + 65);
                }
                crap.push(s);
            }
        }
    
        protected var _removeListener :Boolean;
    
        protected var _otherSprite :Other;
    
        protected var _frameCount :int = 0;
    
        protected var _tf :TextField;
    
        protected var _event :String;
    }
    }
    
    import flash.display.Sprite;
    
    import flash.events.Event;
    import flash.events.EventDispatcher;
    
    class Other extends Sprite
    {   
        public function Other ()
        {
            GarbageTest.genCrap(_crap);
        }
    
        protected var _crap :Array = [];
    }
    

    Running that swf picks two things at random: whether to listen for MouseEvent.CLICK or Event.ENTER_FRAME, and whether to remove the listener or not. The program does a few things and prints memory usage, and at the end the “Finally…” text should print a memory usage similar to what it was at the start of the program.

    What I’m observing is that the sprite is kept around if there is an ENTER_FRAME listener registered on it, but other events do not do this.

    I have a theory as to why this is. I’ve noticed in the past that ENTER_FRAME will be dispatched to a display object even after that object is removed from the hierarchy. I just now noticed that the documentation for ENTER_FRAME on DisplayObject states: Note: This event does not go through a "capture phase" and is dispatched directly to the target, whether the target is on the display list or not. Probably ENTER_FRAME is such an important event that there is an optimization internally in the player to quick-dispatch the event.

    Maybe that is what is preventing the garbage collection.

    In the past I had noticed that while a Sprite may get ENTER_FRAME a few more times after being removed from the display list, it seemed to stop after a second or two. Now I wonder if that means that Sprite is still in memory, but the ENTER_FRAME system just finally noticed that it’s off the list.

    I wonder if there are any other event types that cause this behavior?

  19. Ray Greenwell

    Dang it. How do I post code to wordpress? I looked in the docs and they said <code> and <pre> were the way to go, but they didn’t work for me.

  20. sho

    I fixed it for you, Ray. Do you want me to delete comment #18?

    Great work on figuring out the ENTER_FRAME event listener issue. That is almost certainly the reason these items were not being garbage collected.

    It’s puzzling to me that a sprite would get a couple ENTER_FRAME events after being removed from the display list. It seems like either it should continue to get them forever, or it should stop getting them as soon as they were no longer on the display list. Hmm..

    -Sho

  21. Ray Greenwell

    Sho: sure, delete #18. How did you embed the code? Which tag do I use?

    It seems also that the code in genCrap() got a little mangled.

        public static function genCrap (crap :Array) :void
        {
            for (var ii :int = 0; ii < 10000; ii++) {
                var s :String = "";
                for (var jj :int = 100; jj >= 0; jj--) {
                    s += String.fromCharCode(int(Math.random() * 26) + 65);
                }
                crap.push(s);
            }
        }
    

    And: you’re right. In the above test, the ‘theBlackHole’ function seems to get ENTER_FRAME events until the end of time.

    Perhaps the case I was seeing before was because content loaded into a different application domain can be gc’d if unloaded? I would like to perform some more tests but am holding off right now because I have real work to do.

  22. sho

    Fixed it. Use the <pre> tag for code.

    Thanks for all the research on this issue!

  23. hotfusion

    Nice article! I have been trying to figure this problem out for ages!

    Here is my attempt to find out if a locally declared URLLoader with no other reference would persist.

    // Count the no. of URLLoader created
    var ldrACount:uint = 0
    // Count the no. of URLLoader responses received
    var ldrRCount:uint = 0;
    function ldrGen(e:Event) {
    // Just create 100 instances (I don’t want to hang the machine)
    if (ldrACount

    Here is the php script. It basically sends a no-cache header and a large amount of random data.

    Here is a simple output that I am getting:

    Total: 4 Received: 1
    Total: 4 Received: 2
    Total: 9 Received: 3
    Total: 17 Received: 4
    Total: 24 Received: 5
    Total: 38 Received: 6
    Total: 46 Received: 7
    Total: 48 Received: 8
    Total: 50 Received: 9
    Total: 50 Received: 10
    Total: 51 Received: 11
    Total: 52 Received: 12
    Total: 53 Received: 13
    Total: 54 Received: 14
    Total: 57 Received: 15
    Total: 59 Received: 16
    Total: 65 Received: 17
    Total: 77 Received: 18
    Total: 81 Received: 19
    Total: 92 Received: 20
    Total: 94 Received: 21
    Total: 95 Received: 22
    Total: 97 Received: 23
    Total: 97 Received: 24
    Total: 99 Received: 25
    Total: 99 Received: 26
    Total: 99 Received: 27
    Total: 99 Received: 28
    Total: 100 Received: 29
    Total: 100 Received: 30
    Total: 100 Received: 31
    Total: 100 Received: 32
    Total: 100 Received: 33
    Total: 100 Received: 34
    Total: 100 Received: 35
    Total: 100 Received: 36
    Total: 100 Received: 37
    Total: 100 Received: 38
    Total: 100 Received: 39
    Total: 100 Received: 40
    Total: 100 Received: 41
    Total: 100 Received: 42
    Total: 100 Received: 43
    Total: 100 Received: 44
    Total: 100 Received: 45
    Total: 100 Received: 46
    Total: 100 Received: 47
    Total: 100 Received: 48
    Total: 100 Received: 49
    Total: 100 Received: 50
    Total: 100 Received: 51
    Total: 100 Received: 52
    Total: 100 Received: 53
    Total: 100 Received: 54
    Total: 100 Received: 55
    Total: 100 Received: 56
    Total: 100 Received: 57
    Total: 100 Received: 58
    Total: 100 Received: 59
    Total: 100 Received: 60
    Total: 100 Received: 61
    Total: 100 Received: 62
    Total: 100 Received: 63
    Total: 100 Received: 64
    Total: 100 Received: 65
    Total: 100 Received: 66
    Total: 100 Received: 67
    Total: 100 Received: 68
    Total: 100 Received: 69
    Total: 100 Received: 70
    Total: 100 Received: 71
    Total: 100 Received: 72
    Total: 100 Received: 73
    Total: 100 Received: 74
    Total: 100 Received: 75
    Total: 100 Received: 76
    Total: 100 Received: 77
    Total: 100 Received: 78
    Total: 100 Received: 79
    Total: 100 Received: 80
    Total: 100 Received: 81
    Total: 100 Received: 82
    Total: 100 Received: 83
    Total: 100 Received: 84
    Total: 100 Received: 85
    Total: 100 Received: 86
    Total: 100 Received: 87
    Total: 100 Received: 88
    Total: 100 Received: 89
    Total: 100 Received: 90
    Total: 100 Received: 91
    Total: 100 Received: 92
    Total: 100 Received: 93
    Total: 100 Received: 94
    Total: 100 Received: 95
    Total: 100 Received: 96
    Total: 100 Received: 97
    Total: 100 Received: 98
    Total: 100 Received: 99
    Total: 100 Received: 100

    I am always receiving 100 response, and notice how I am still receiving responses when the total hit 100. I think it’s a pretty good proof that active URLLoader is immune from garbage collection.

    Any comment?

  24. hotfusion

    Sorry, but the pre tag doesn’t work for me! Could you please fix my last post?

  25. Florecista’s Weblog

    […] Asynchronous calls explained […]

  26. fahad

    hmm , well people .. i tried creating a bindable boolean that, if ‘false’ will just waste the loop cycles and when the result method is executed .. its turned to a ‘true’ value …
    //**********************************************calling myltiple streams
    while(stream.bytesAvailable)
    if(flag == false)
    continue;
    else
    {
    //read
    //send another request
    }
    //*************************on result of each stream

    onresult()
    {
    //perform action

    flag = true;
    }

    // it should work.. but i just tried it and flex builder hangs :D
    // not to mention this is the simplest way !

  27. Balint

    Sho, pending variable is really needed in strategy 3?

  28. bgmobile.eu

    Heaps of features on these steam showers, I love the multimedia idea and the lighting style

    Visit my site :: steam shower with whirlpool tub (bgmobile.eu)

  29. Raiizza

    I’m imsrepsed you should think of something like that

  30. http://www.ionicbathfootdetox.com/

    I should say, youve got one with the best blogs Ive observed in a lengthy time. What I wouldnt give to have the ability to generate a blog thats as interesting as this. I guess Ill just have to keep reading yours and hope that one day I can write on a subject with as significantly knowledge as youve got on this 1!

Leave a Reply