• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Tim Cooke
  • Liutauras Vilda
  • Jeanne Boyarsky
  • paul wheaton
Sheriffs:
  • Ron McLeod
  • Devaka Cooray
  • Henry Wong
Saloon Keepers:
  • Tim Holloway
  • Stephan van Hulst
  • Carey Brown
  • Tim Moores
  • Mikalai Zaikin
Bartenders:
  • Frits Walraven

"Serving" Remote Objects

 
Ranch Hand
Posts: 77
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Please comment on the following:
Scenario
��������
I use a ConnectionFactory object bound to the RMI registry.
Question
��������
Should the ConnectionFactory return a *new* instance of my RemoteDataImpl or just a reference to a single, private instance of RemoteDataImpl (i.e. via a Factory method) for each client request?
If a new instance is served to each client, how can the locking mechanism track which instance has control of the DB?
Thanks for the help.
 
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Yes, do return new objects. Then, use the Remote Object itself to ID the client when it locks the record. You can do this by either using the LockManager, or a static hashmap inside if the Data class.
M
 
Christian Garcia
Ranch Hand
Posts: 77
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Max,
Thanks for the repsonse. I had a feeling that returning new objects was the way to go, but with all the information floating around the Ranch, things get convoluted. That said, I have a better understanding as to the role my ConnectionFactory plays.
I have a side-question: My RemoteDataImpl wraps an instance of the Data class along with implementing Lock/Unlock directly. The functions defined in RemoteDataImpl dispatch to the corresponding functions in the instance of the Data class.
Please comment on this implementation.
Thanks for the help.
[ May 29, 2003: Message edited by: Christian Garcia ]
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi Christian,
That sounds ok: you're creating an Adapter for the Data class. However, I am curious: why didn't you implement lock/unlock in Data? I'm not suggesting it's wrong, but I'd like to hear your reasoning.

All best,
M
[ May 31, 2003: Message edited by: Max Habibi ]
 
Ranch Hand
Posts: 127
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I implemented lock/unlock methods inside Data class and it works fine. I used HashMap and initialized in Data constructor.
1. Now, if for any reason a client crashs while he is in locking process (and before unlock() ), So what shall we do?
Some members here said about using Unreferrenced interface, (which it takes about 20 minutes to unlock the client). Where shall this interface implement and it seems unreasonable to hold (lock) the record for 20 minutes?
Is there any idea about solving client crashing?
2. for locking whole Database by using (-1), it seems it is an administration purposes which is the server responsibility, BUT How (-1) will be pass to lock() method. Nothing in the instruction menthioned who will pass (-1). IMO, just implementation for Database locking in lock() method is enough...
Any ideas please...
Regards
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Instead of a HashMap, use a WeakHashMap: everything else will fall into place.
M
 
Qusay Jaafar
Ranch Hand
Posts: 127
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Is:
Map m = new WeakHashMap();
is enough or am I missing something here.
How can I test this WeakHashMap() working.
I created two threads.
new FirstThread().lock(7);
new SecondThread().lock(7);
new SecondThread().unlock(7);
The result here:
SecondThread still waiting for record(7) to unlock.
is this senario for FirstThread is similar if the connection of a client as disconnected, (it seems not) or What? I think I need help here...
What about my second Question about Database lock?
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Qusay Jaafar:
Is:
Map m = new WeakHashMap();
is enough or am I missing something here.
How can I test this WeakHashMap() working.
I created two threads.
new FirstThread().lock(7);
new SecondThread().lock(7);
new SecondThread().unlock(7);
The result here:
SecondThread still waiting for record(7) to unlock.
is this senario for FirstThread is similar if the connection of a client as disconnected, (it seems not) or What? I think I need help here...
What about my second Question about Database lock?


Hi Qusay,
1. Yes, the Map m = new WeakHashMap is enough. To test it, try bringing up two+ clients in seperate threads(maybe in a test program), have one lock a record, crash, and then call the gc(server side, of course) a couple of hundred times. Depending on how your system works with the gc, this should simulate the condition. However, this whole path isn't required, and you should be able to earn a perfect score by not doing it.
2. As to your second question. Yes, implement it, but don't provide access in the GUI. You weren't asked to, and thus shouldn't.
All best,
M
[ June 01, 2003: Message edited by: Max Habibi ]
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Christian Garcia:
Max,
I decided not to implement lock/unlock directly in the Data class because it seemed (to me) more logical to include those functions in a server-side implementation. As far as I can tell, there isn't any reason to lock and unlock records on a local machine. In retrospect I doubt that is a valid defense of a design decision, but if you care to point out some caveats with it I'd appreciate it.


I think that a lockmanager is just fine: it's not my choice, but it is a valid one. However, just for discussion's sake, you are aware that even as Data is a remote object, it executes serverside?



Since I last posted I took my design a step further. Now, I have a DataAccessFactory on the client-side and a ConnectionFactory registered with the RMI registry on the server-side. I use the DataAccessFactory to return remote or
local references of type DataInterface. This is achieved by calling an overloaded
getConnection() method that either takes a path to a local database or a String[] containing the server DNS and the registered name of the
ConnectionFactory in the RMI registry.
My RemoteDataInterface implements DataInterface which creates a nice "transparency" between local and remote instances. Essentially, when my
Controller creates an instance of the Model (the Data adapter) it has no idea where the Model reference came from only that it has one.
Thanks for your replies and for a great book.


Sounds great: nice job
M
 
Christian Garcia
Ranch Hand
Posts: 77
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Max,
I decided not to implement lock/unlock directly in the Data class because it seemed (to me) more logical to include those functions in a server-side implementation. As far as I can tell, there isn't any reason to lock and unlock records on a local machine. In retrospect, I doubt that is a valid defense of a design decision, but if you care to point out some caveats with it I'd appreciate it.
Since I last posted I took my design a step further. Now, I have a DataAccessFactory on the client-side and a ConnectionFactory registered with the RMI registry on the server-side. I use the DataAccessFactory to return remote or local references of type DataInterface. Since my RemoteDataInterface implements DataInterface, this creates a nice "transparency" between local and remote instances. Essentially, when my Controller creates an instance of the Model (the Data adapter) it has no idea where the Model reference came from only that it has one.
This works well, but now I've got myself in a bit of a quandry. To get the ConnectionFactory to work I had to extend UnicastRemoteObject and implement the Remote interface. My server- side "RemoteDataImpl" already does the same thing because prior to today I was returning instances of it directly.
Having two classes flagged as remote with one handing out instances of another doesn't seem at all correct. My best guess is to modify RemoteDataImpl to be a "regular" object and let ConnectionFactory take the roll of being "Remote".
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator


Having two classes flagged as remote with one handing out instances of another doesn't seem at all correct. My best guess is to modify RemoteDataImpl to be a "regular" object and let ConnectionFactory take the roll of being "Remote".


Don't you use Home in ejbs? It's really nothing but a factory: it just happens to be a remote factory.
 
Christian Garcia
Ranch Hand
Posts: 77
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Max,
I haven't used EJBs before. Can you explain a little more?
Thanks for the help.
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
It's the same principle, really. You ask one remote object, the *home object, to create instances of remote objects for you. Thus

These home interface are specialized factories, (which you don't really need in the SCJD assignment: one factory is enough), and they create instances of the remote Objects for you. I always figured that the SCJD assignment was sort of pushing us in this direction anyway, so as to 'pave the way' for understanding ejbs. Taking this approach, you could argue that you're building a mini (very limited) app server here.
M
 
Christian Garcia
Ranch Hand
Posts: 77
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Max,
So, given our explanation, only the ConnectionFactory is registered with the RMI Registry and returns instances of RemoteDataImpl.
Is this correct?
[ June 02, 2003: Message edited by: Christian Garcia ]
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I always bound the objects before returning them. That is, I had a private static int counter in the factory, and I used it to create unique names for the RemoteData objects. Thus, "RemoteData1", RemoteData2", and so on. I would then pass in each object's unique name to it as a member variable.
Thus, the close method on the RemoteData object would unbind it when the object was released. This way, you get the benefits of RMI controlling the life span of your object.
All best,
M
 
Christian Garcia
Ranch Hand
Posts: 77
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
So, what's happening is:
1. Each new instance of RemoteData is bound to the Registry at run-time.
2. ConnectionFactory returns the new instance of RemoteData.
Do I have this right?
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Sounds good.
M
 
Christian Garcia
Ranch Hand
Posts: 77
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Max,
Can you foresee any issues with this implementation?
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
You mean, do I think I gave you bad advice? . Well, maybe: but if I had, I don't think I would have advised it. But heck, it's just a suggestion: you should't let my advice overcome your own judgement. take the idea for a spin, kick the tires, see how it feels for you. How you design should be an expression of who you are, not what I think you should do.
All best,
M
 
Christian Garcia
Ranch Hand
Posts: 77
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Max,
I was just fishing for a "devil's advocate" response. The problems I've run into haven't all been figuring out how to get things to work, but figuring out "why" to implement functionality in particular way.
Thank you for your advice and for the patience. I'm doing my best at getting thru this challenge.
P.S.
Good book. It's helped me out big-time.
 
Greenhorn
Posts: 22
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Max Habibi:
I always bound the objects before returning them. That is, I had a private static int counter in the factory, and I used it to create unique names for the RemoteData objects. Thus, "RemoteData1", RemoteData2", and so on. I would then pass in each object's unique name to it as a member variable.
Thus, the close method on the RemoteData object would unbind it when the object was released. This way, you get the benefits of RMI controlling the life span of your object.
All best,
M


Hi Max
I would be extremely grateful if you could go into more detail here. In my implementation, I only bind the RMI factory instance to the registry. It simply returns remote data adapter objects as required (seems to work OK). What are the benefits of doing as you suggest? Also, what is the down side of not doing so?
Many Thanks
Jamie
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Mind you, I'm not positive on this, but I think you lose the benefit of guaranteed distributed garbage collection: that is, if a client looses connection, and their object is bound to the registry, then RMI will eventually release that remote object. Not sure that you're still a candidate for the service if you don't bind.
M
 
Qusay Jaafar
Ranch Hand
Posts: 127
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Is this will solve the problem of a client who has a lock of a record and then crashed for any reason before unlock the record? I mean is the lock of the record will be released?
 
Qusay Jaafar
Ranch Hand
Posts: 127
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
By the way, shall I run gc into the test program too or where exactly? please give more details about this problem
Another Question..
I read that using Unreferenced interface will not work while Client A which crashes and Client B blocks inside the server...
is that right? if it is, so what is the meaning for Unreferenced interface?
 
Qusay Jaafar
Ranch Hand
Posts: 127
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
any idea?
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Qusay Jaafar:
any idea?



Sorry Qusay, completely forgot about this thread.


Is this will solve the problem of a client who has a lock of a record and then crashed for any reason before unlock the record? I mean is the lock of the record will be released?


Yes, if you use a WeakHashMap or Unreferenced to store the data originally: say using a map structure where the Key is the remote Object, and the value is the locked record. In the case of a WeakHashMap, one the remote object is collected by RMI, the key will be released.


I read that using Unreferenced interface will not work while Client A which crashes and Client B blocks inside the server...
is that right? if it is, so what is the meaning for Unreferenced interface?


This is a complicated question. Let me start by answering the second, and then I'll ask you to break down exactly what you mean by the first part.
Say I have

In this case, the actual object has two variables referring to it: the o variable, and the o1 variable. Thus, both o and o1 would have to be set to null(or go out of scope), before the actual memory was reclaimed by the gc. In the short explanation, it's possible to avoid that second reference by using an Unreferenced interface. Does that make sense so far?
ps - yes, for the experiment, the gc should be run by the server.
M
 
Qusay Jaafar
Ranch Hand
Posts: 127
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Will you tell me please how I test this case by using a WeakHashMap?
Is WeakHashMap will remove the remote object immediately once it disconnects?
It seems using WeakHashMap is easier than using Unreferenced interface with its:

 
author and jackaroo
Posts: 12200
280
Mac IntelliJ IDE Firefox Browser Oracle C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Qusay: Will you tell me please how I test this case by using a WeakHashMap?


Take a look at Max's comment posted June 01, 2003 04:24 PM. Max describes one way of testing it there.

Qusay: It seems using WeakHashMap is easier than using Unreferenced interface


It is not always one or the other, so the category "easier" may not be valid.
The WeakHashMap will automatically remove the keys for you, but the downside is that it is done automatically: you are unaware of when it is done.
Whereas for unreferenced, your code becomes explicitly aware that the client is dead, and can do any work you decide is necesary. The downside is that you have to do the work.
Now what happens in the following scenario: client 'A' locks record '1'. Client 'B' attempts to lock record '1' - it cannot get it, so it calls wait(). Client 'A' dies.
With WeakHashMaps, lock '1' will be removed the next time the garbage collector runs on the server. But client 'B' is still waiting to be notified - it could be waiting a long time. Of course, you could get around this by changing the lock() method a little bit. But you may not be able to with the new assignment's instruction that the current thread gives up the CPU and consumes no CPU cycles until the record is unlocked.
With unreferenced, your code on the server gets notified that client 'A' is dead. You then have to unlock any locks that client 'A' held. As long as your unlock method calls notify then client 'B' will be able to continue.
By the way Max, I am quite impressed with your psychic abilities: you have a reply to Christian posted June 01, 2003 06:55 PM, in reply to his post sent 15 minutes after your response
Regards, Andrew
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
/*


The WeakHashMap will automatically remove the keys for you, but the downside is that it is done automatically: you are unaware of when it is done.
Whereas for Unreferenced, your code becomes explicitly aware that the client is dead, and can do any work you decide is necessary. The downside is that you have to do the work.


This is somewhat misleading, because when your code becomes explicitly aware of an Unreferenced is also automagic: thus, you are still unaware of when it's done. Both solutions require that you depend on the JVM to 'notice' when the object ref is released. The Unreferenced approach requires you to write code, while the WeakHashMap doesn't. Also, the Unreferenced implemation can get called several times. More on this later.
Along the same lines, the unreferenced(if it's to be implemented correctly) requires some fairly tricky coding, so that you don't try to release a connection that is already done. If I'm not mistaken, I'm too lazy to dig up the thread on this right now, but it's been discussed here before.


Now what happens in the following scenario: client 'A' locks record '1'. Client 'B' attempts to lock record '1' - it cannot get it, so it calls wait(). Client 'A' dies.
With WeakHashMaps, lock '1' will be removed the next time the garbage collector runs on the server. But client 'B' is still waiting to be notified - it could be waiting a long time.


Again, the same is true for the unreferenced mechanism.


Of course, you could get around this by changing the lock() method a little bit. But you may not be able to with the new assignment's instruction that the current thread gives up the CPU and consumes no CPU cycles until the record is unlocked.


I don't at all agree that this is called for. It is, IMO, a bad idea to change the method signatures of the methods Sun provided. That is, in essence, refusing to solve the problem that they asked you to solve, in leui of solving another, problem that's more to your liking.


With Unreferenced, your code on the server gets notified that client 'A' is dead. You then have to unlock any locks that client 'A' held. As long as your unlock method calls notify then client 'B' will be able to continue.


Again, this can call for some tricky thread coding, because the actual reference can disappear out from under you. Worse, you're nesting locks here(one for the lock/unock, another for the necessary synchronization on the reference to keep it in memory until you're done). All in all, I'm still a firm believer in using the WeakHashMap with the client as a key. Presto, you're done.
From the sun site http://java.sun.com/j2se/1.3/docs/guide/rmi/spec/rmi-server5.html

As such, the unreferenced method can be called more than once (each time the set is newly emptied). Remote objects are only collected when no more references, either local references or those held by clients, still exist.


Basically, Unreferenced can be called multiple times, so that's something you have to guard against. The WeakHashMap implementation doesn't suffer from this malady.


By the way Max, I am quite impressed with your psychic abilities: you have a reply to Christian posted June 01, 2003 06:55 PM, in reply to his post sent 15 minutes after your response


Similarly, my wife has always been impressed by my ability to anticipate when I going to need to bring flowers home: She doesn't understand that I can just 'sense' that I'm in trouble .
But seriously, I know that Unreferenced is popular as a design choice for some good posters here, so I'm disinclined to disagree to0 strongly: to each thier own. However, in my humble opinion, it's not the best approach.
All best,
M
 
Andrew Monkhouse
author and jackaroo
Posts: 12200
280
Mac IntelliJ IDE Firefox Browser Oracle C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi Max

Andrew: The WeakHashMap will automatically remove the keys for you, but the downside is that it is done automatically: you are unaware of when it is done.
Max: This is somewhat misleading, because when your code becomes explicitly aware of an Unreferenced is also automagic: thus, you are still unaware of when it's done.


Darn English language
What I meant was that with WeakHashMaps, after the client dies, sometime later the key should be removed from the map. But your code does not get notification of this.
Wheras with Unreferenced, after the client dies, sometime later your unreferenced method gets called. So you can then wake up any threads that might be waiting for a lock.

Andrew: Of course, you could get around this by changing the lock() method a little bit.
Max: I don't at all agree that this is called for. It is, IMO, a bad idea to change the method signatures of the methods Sun provided.


Sorry for any confusion, I wasn't talking about changing the signature of the method. I was talking about changing the implementation of the method, which I had earlier described one possible implementation which was just calling wait() if the desired record was already locked.
I agree with you that changing the signatures is a big no no.

The Unreferenced approach requires you to write code, while the WeakHashMap doesn't.


Personally I found a simple solution where the code I had to write for unreferenced was easy and clean. However it did mean that I was traking locks in two locations - one in the Data class, and one in the RMI object. I actually think this is a benefit, since it does not require the Data class's lock to track who owns a given lock. But that is my opinion.
I still don't see how in the scenario I gave before, client 'B' will ever get the lock on record '1', unless it is using a wait() with a timeout.

Max: But seriously, I know that Unreferenced is popular as a design choice for some good posters here, so I'm disinclined to disagree to0 strongly: to each thier own. However, in my humble opinion, it's not the best approach.


I am one of those who likes Unreferenced instead of WeakHashMaps, but I can also see the good points in your arguments.
Regards, Andrew
 
Qusay Jaafar
Ranch Hand
Posts: 127
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
thank you for your comments but I need help in some points.
I create more than two threads and I locked one of them but I don't know how I simulate this thread as to be crashed. then I called gc().
This is the implementation of run() method:

getGC() method is in ConnectionFactoryImpl class which is:

what is missing here? (specially about how to crash the thread titled "1"???
 
Christian Garcia
Ranch Hand
Posts: 77
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Wow, this thread blossomed into a fine debate
I followed a design in which, I too, use a static HashMap that is synchronized on when a client locks a given record. My remote implementation of the Data class implements the Unreferenced interface. Within the 'unreferenced()' method I perform a lookup against the HashMap to determine if the current instance of RemoteData is a key within it (Each element of the HashMap is identified as [RemoteData,RecordNumber]). If so, the key is removed and notifyAll() is called. In doing this, any lock that ClientA held before it died is released enabling ClientB to obtain the lock it was waiting for.
I can see using a WeakHashMap has the benefit of 'auto-magic' removal of 'dangling' key/value pairs when the GC runs. However, IMO using the Unreferenced interface provides control over exactly what happens when the remote object becomes invalid. I tested this with 50 or so threads all competing for the same lock on the same record. Intermittently, a thread would die without releasing the lock and the next thread would wait. It didn't take 20 minutes for unreferenced() to be called and the lock released.
[ June 20, 2003: Message edited by: Christian Garcia ]
 
Put a gun against his head, pulled my trigger, now he's dead, that tiny ad sure bled
Gift giving made easy with the permaculture playing cards
https://coderanch.com/t/777758/Gift-giving-easy-permaculture-playing
reply
    Bookmark Topic Watch Topic
  • New Topic