• 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
  • Jeanne Boyarsky
  • Ron McLeod
  • Paul Clapham
  • Liutauras Vilda
Sheriffs:
  • paul wheaton
  • Rob Spoor
  • Devaka Cooray
Saloon Keepers:
  • Stephan van Hulst
  • Tim Holloway
  • Carey Brown
  • Frits Walraven
  • Tim Moores
Bartenders:
  • Mikalai Zaikin

Who's using threads?

 
author
Posts: 11962
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi all,

I see that the book promotion has driven everyone into this quiet little forum so I'll exploit the situation by asking people a little thread-related question:

Are you writing threaded code and what for?

The reason I'm asking is that while I consider multi-threading to be an essential part of any programming language/platform, people rarely seem to need those skills in business application development projects. In my experience, a typical J2EE developer doesn't need to actually write threaded code. Personally, I've written more than usual but that's just because I have "specialized" in backend/integration/billing systems and haven't done much "web" development during the past couple of years.

The multi-threading problems I've had to program my way through have mostly been trivial "batch" operations where somebody polls a service/directory/whatever according to a schedule, proceeds to gather a batch of work to do, and schedules the work into a queue to be done by a shared worker thread pool.
 
Ranch Hand
Posts: 808
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi, Lasse -

I generally use threads in applets and GUIs, especially when getting data from remote sources that could either timeout or cause visible delay. I'm writing my first multi-user server for the SCJD exam, so I'll use threads there too.

Cheers,

Jeff
[ October 21, 2004: Message edited by: Jeff Bosch ]
 
Ranch Hand
Posts: 995
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi Lasse !

I'll present you one of the most interesting things I have done with threads. Our application has to present on a Swing client a number of database tables diagrams and/or table relations diagrams. While table diagram are computed immediately - as there is no need of computing a graph layout, displaying a relationship diagram was quite annoying - taking between 15 sec up to 7 minutes (for diagrams with more than 500 tables). So we have decided to allow some background threads to take care of computing layouts for complicated diagrams (how we determine the threshold for complicated vs simple diagrams is another story). The memory consumption was not dangerous so we were able to go for this solution.
The interesting problems I have met were: what happens if the client want to access a diagram which was not done yet, how do I stop in the shortest time the background threads if the client leaves the diagram module and possible many more I cannot remember now.

./pope
 
Ranch Hand
Posts: 1209
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
yup i agree. I also made a post along similar lines. I never had to write threaded code on projects (web based j2ee). But yeah had to debug threading issues because of an error on the part of developers (+ in our case , an issue with a library that we were using).
Would be great if people could share their experiences.
 
(instanceof Sidekick)
Posts: 8791
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
A few real examples from my work life ...

With Swing you almost always want to get significant work off the Swing thread, so you start touching new threads.

We like to do some operations asynchronously so the user doesn't have to wait. We collect a lot of information about a work session and save it on another thread so the user can start the next session more quickly.

We have a queuing engine to send work from one user to another or sometimes to another module in our system. A queue monitor starts new threads for the other modules to work on.

I have a program that downloads log files. I can give it a huge list of files and it dowloads "n" at a time with a fixed size thread pool. It truly takes "1/n" as much time to do the downloads because I haven't touched my local bandwidth limits yet and the CPU is still waiting for data.

I have another program that periodically re-indexes my web site for a search engine. It runs on Timer threads.

Agreed - you can do an awful lot of useful stuff with no threads. I spent years working with languages that didn't have them. But now that I have em, I love em!
 
Alexandru Popescu
Ranch Hand
Posts: 995
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I have forgotten to mention that even if I did a lot of J2EE development - since 2000 - I have never used programatically threads in J2EE solutions. Before the release of MDBs some times I have had to redesign the solution presented by high level architects in order to avoid this.

./pope
 
author and iconoclast
Posts: 24207
46
Mac OS X Eclipse IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
- A desktop app has a command queue that services requests from the GUI.

- Several sections of that same app are compute-intensive, and many of our users have dual-processor Xeons, so we run the calculation in parallel in two threads so it goes twice as fast.

- Although Jess doesn't create any threads, users frequently use it in a multithreaded environment. Because it uses a data-intensive and very stateful algorithm, Jess makes fairly sophisticated use of multiple locks and synchronized sections to protect itself while ensuring high availability.

Other than small fairly incidental things, I've never done any real J2EE development. I guess this puts me in the minority here.
 
Lasse Koskela
author
Posts: 11962
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thanks guys! It wasn't a surprise to hear Swing applications using threads since that's the only way to go, but I was a bit surprised to hear that so many of you are working with Swing in general. I haven't seen a desktop client in a while. Oh, actually, I did. In February/March, in fact. But that particular application was so plain and simple that it probably ended up without any threading at all (I mean really simple!).
 
Alexandru Popescu
Ranch Hand
Posts: 995
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hmmm... what IDE are you using Lasse? In case you are using IntelliJ IDEA or Netbeans you're seeing everyday threads on a Java Swing application .

./pope
[ October 21, 2004: Message edited by: Ali Pope ]
 
Alexandru Popescu
Ranch Hand
Posts: 995
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I have just received the JavaRanch Journal for October and I have noticed here Java Designs - Synchronized Multithreading with Swing

./pope
 
Lasse Koskela
author
Posts: 11962
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Ali Pope:
Hmmm... what IDE are you using Lasse? In case you are using IntelliJ IDEA or Netbeans you're seeing everyday threads on a Java Swing application .


But I haven't seen anyone developing IDEA or Eclipse...
 
Alexandru Popescu
Ranch Hand
Posts: 995
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I am a contributor for a few Eclipse plugins - but till now none of them required thread mangement till now.

./pope
 
author
Posts: 23951
142
jQuery Eclipse IDE Firefox Browser VI Editor C++ Chrome Java Linux Windows
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I agree, even though threads are very well integrated into Java. Most people don't consider it until they need it.

The largest effort for me was probably about 10 years ago. Multiprocessors machines were starting to become common. And I probably spent a full two years working with different customers getting their programs -- mostly financial simulations -- to run with threads. Of course, back then, it was done in C, using solaris threads, and later with POSIX threads.

Another place where this pops up is with server side applications. Particularly, those that service lots of clients. Today, I am not sure if this is necessary for the majority of the cases. Many servers today will just run multiple copies of the program on the server side.

And finally, I agree that the largest effort is probably with Swing applications. The unfortunately part here is that many developers don't even know there are even threading issues with swing. And when they do, they just learn to use one or two method calls of the swing utilities class to get their apps working.

Henry
 
Henry Wong
author
Posts: 23951
142
jQuery Eclipse IDE Firefox Browser VI Editor C++ Chrome Java Linux Windows
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Ohh... and to add another story... which happen many many years ago...

I onced worked with a client that had a really IO bounded application, and decided to create many threads to increase the throughput...

Henry
 
Ranch Hand
Posts: 1170
Hibernate Eclipse IDE Ubuntu
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I can only think of 2 reasons.

1. responsiveness. This is why GUI programmers or GUI users have threads. Also DB programmers use threads for this reason since DB access can block and sometimes be delayed. file io.

The most important reason though and one that is vastly underused is to prevent lockup in case the network disappears. Microsoft is NOTORIOUS for creating code that locks up when the network is not present or disappears for a while. None of my code EVER locks when the network disappears. Like a pet peeve I suppose.

2. parallel processing. Faster calculations.
 
Ranch Hand
Posts: 867
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
hi Lasse Koskela
Are you writing threaded code and what for?
I have finished a project in c++ programming and the purpose of this project is used to show out how to create a process that instantiates n threads: writer threads and reader threads.
I require to do two different version that is following
1: Writers with Priority:
Writers have priority. (When a reader requires reading and there is already a writer visiting the resource, it must wait unitl there is no writer waiting. Reader should not be preempted.

2: Readers with Priority:
Reader have priority. (When a reader is waiting, writers can not enter the resource and they are not preempted if one of the writer are already writing. When a reader requires reading and another reader has already been reading, it can start reading directly.
 
Alexandru Popescu
Ranch Hand
Posts: 995
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I am not quite sure if C++ and Java thread management are in the same direction
It is quite obvious that the theory is the same, don't know about practical side.

./pope
 
Lasse Koskela
author
Posts: 11962
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Ali Pope:
I am not quite sure if C++ and Java thread management are in the same direction
It is quite obvious that the theory is the same, don't know about practical side.


I've written just one very simple "chat server" type of threaded application in C/C++ (using pthreads) and I have to say it was pure hell compared to Java's Thread and Runnable. Well, I guess the biggest part of the difference was actually the language and GC, not necessarily the threading API itself.
 
Ranch Hand
Posts: 724
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I think C++ could be a hell in everything compared to Java, not only the Threads
 
Francis Siu
Ranch Hand
Posts: 867
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Which programming language thread is faster?
C>C++>Java
As I know, java thread seems to be encapsulated much layers, isn't it? And I want to know more about that
Will the GC collect the thread after the thread have finished to run the program in Java?
Comparing with C++, does java implement the thread easiler than c++?
thanks for your time
 
Alexandru Popescu
Ranch Hand
Posts: 995
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Lasse Koskela:

I've written just one very simple "chat server" type of threaded application in C/C++ (using pthreads) and I have to say it was pure hell compared to Java's Thread and Runnable. Well, I guess the biggest part of the difference was actually the language and GC, not necessarily the threading API itself.



Yep this is indeed what I have supposed. 10x you confirmed Lasse.

./pope
 
Lasse Koskela
author
Posts: 11962
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by siu chung man:
Which programming language thread is faster?
C>C++>Java

As I know, java thread seems to be encapsulated much layers, isn't it?

In theory, a compiled language such as C or C++ is indeed faster than an interpreted language like Java (bytecode). However, in practice, for most application domains the difference is negligible.

Originally posted by siu chung man:
Will the GC collect the thread after the thread have finished to run the program in Java?

The GC collects thread objects just like any other object within the JVM.

Originally posted by siu chung man:
Comparing with C++, does java implement the thread easiler than c++?


Much easier.
 
Bartender
Posts: 10336
Hibernate Eclipse IDE Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
An answer to your original question, "am I writing threaded code"? The answer is: almost never.

I can think of a couple of times in the last six or seven years where I've written threaded code in Java - both were written about six years ago and both were for providing functionality now offered by containers and other third party libraries. I can see why its essential to Swing/AWT programmers, but not for anyone who works in the J2EE world (let me qualify that, by "not essential", I mean "don't need to use directly" rather than "don't need to know").
 
Ranch Hand
Posts: 241
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Paul Sturrock:
I can see why its essential to Swing/AWT programmers, but not for anyone who works in the J2EE world (let me qualify that, by "not essential", I mean "don't need to use directly" rather than "don't need to know").



Paul,
I think that, in j2ee applications, threads are so transparent that you don�t need to know anything about threads.
Could you give me an example of an improvement of an application if the programmer knows some things of thread ?

Regards,
 
David Ulicny
Ranch Hand
Posts: 724
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I think you should know about threading issues, if you are accessing the database and there is a long running query, you should know that this place could be a bottleneck if synchronize it.
 
Alexandru Popescu
Ranch Hand
Posts: 995
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by David Ulicny:
I think you should know about threading issues, if you are accessing the database and there is a long running query, you should know that this place could be a bottleneck if synchronize it.



Indeed, but as some of us already told there are specific indication not not to use thread management inside the container (thread management includes the synchronization)

./pope
 
Alexandru Popescu
Ranch Hand
Posts: 995
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by siu chung man:
Which programming language thread is faster?
C>C++>Java



Probably C/C++ are faster as java after doing its internal stuff will finally use system threading resources in the same way c/c++ does. Can you notice this performance difference? I guess you don't.

./pope
 
Eusebio Floriano
Ranch Hand
Posts: 241
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Ali Pope:


Indeed, but as some of us already told there are specific indication not not to use thread management inside the container (thread management includes the synchronization)

./pope



Exactly Ali Pope,
At least in a ejb container, you are not supposed to use threads.
i can�t see where a knowledge of threads could be applyied.

Regards,
 
Francis Siu
Ranch Hand
Posts: 867
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Can you notice this performance difference? I guess you don't.

I did not try to write Java thread. As I know that the concept is same as c and c++. If comparsion the performance difference, I do not know how to compare, as I just learn Big O notation to do analysis the performance between two algorithms. This analysis bases on the same programming language.

Could you share your idea on how to compare the performance difference between using c, c++ and Java thread?
I want to know more about it.
thanks
 
Paul Sturrock
Bartender
Posts: 10336
Hibernate Eclipse IDE Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator


Could you give me an example of an improvement of an application if the programmer knows some things of thread ?


I didn't say that a knowledge of threading in Java would somehow make a J2EE application in anyway better. What I meant was programmers shouldn't not learn Java's threading stuff simply because containers render the knowledge very infrequently applied.
 
Eusebio Floriano
Ranch Hand
Posts: 241
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Paul Sturrock:

I didn't say that a knowledge of threading in Java would somehow make a J2EE application in anyway better. What I meant was programmers shouldn't not learn Java's threading stuff simply because containers render the knowledge very infrequently applied.



Paul,
Actually, my point was not if you are right or wrong. The point is if you or anyone else can give me an example of the knowledge of thread could be applied to improve the quality of the applition.

Regards,
 
Alexandru Popescu
Ranch Hand
Posts: 995
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Vinicius, even if we have agreed on some of the above points I feel that knowing thread principles is a must for all developers. Without knowing this you will not be able to understand the need of JMS or MDB, not to talk about understanding how they really work.

./pope
 
blacksmith
Posts: 1332
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Interesting question ...

Everyone who works with a web application or a Swing application uses threads, even if they don't know it. Even in the simplest swing applications, the event handling thread is a different thread than the startup thread. And in a web application, usually there are multiple threads available for execution of multiple requests concurrently.

Usually this doesn't matter. Sometimes it does, though ... I'm presently troubleshooting a problem that occurs when two different web threads try to execute a logout concurrently. And since it's an as yet untested port of 10 years of accumulated MSVC++ code, it isn't the simplest thing to track down.

I'm also writing multithreaded code for another project, but I need to see if the test server has rebooted so I can run another test now.
 
Lasse Koskela
author
Posts: 11962
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Vinicius Boson:
Actually, my point was not if you are right or wrong. The point is if you or anyone else can give me an example of the knowledge of thread could be applied to improve the quality of the applition.


Here's an example:
You're developing a web application that provides its users a web interface for performing long-lasting, CPU-intensive computation -- let's say some kind of 3D rendering on top of a huge statistics database based on given input parameters just to give you an idea of what I mean.

Now, general knowledge of multithreading is definitely needed so that the developer won't leave the request processing thread block for 5 minutes while the computation is being carried out. Instead, the developer should quite probably hand off the job of carrying out the long-lasting computation to a pool of worker threads processing the scheduled computations in the background while the web container's request processing threads are free to serve other clients.

So, the developer needs to know general stuff about multithreading and perhaps know how to use JMS or MDB. However, technically he doesn't need to know how to instantiate a java.lang.Thread, for example.
 
Lasse Koskela
author
Posts: 11962
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Clarification to my original question:
I'm talking about direct usage of java.lang.Thread, java.lang.Runnable, java.lang.ThreadGroup, etc., not indirect usage of threads a la MDBs or JMS.
 
Stan James
(instanceof Sidekick)
Posts: 8791
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
The EJB spec tells you not to muck with threads, and I think some implementations now throw execptions if you try. Still you are coding within a powerful multi-threaded environment and it's good to know some thread basics like don't share member variables in servlets, but you can use them in EJBs, you can tune the thread pool sizes for EJBs, what happens if your bean methods run too long and all threads are consumed ...

But life is not all EJB, thank goodness. My love for background processing goes back to OS/2 before Warp with an app that was seven executables on the user's PC communicating via DDE in a star network.

One more real life in servlet/EJB world: We had a recent requirement to send a broadcast message to various subsets of users of the system. One user hits the button and the server sends the msg to everyone in the set. The vendor framework included a callback mechanism to push messages to an applet so we used that. But sending these messages one at a time could take minutes - too long for the first user to wait and maybe too long for all to get the msg. So we divided the user list into "n" parts, made "n" threads in our servlet that called "n" EJBs to do the work on sublists and returned a "working" message to the user in a matter of seconds.
 
Alexandru Popescu
Ranch Hand
Posts: 995
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Lasse Koskela:

Here's an example:
[...]



Very interesting problem Lasse. How do you really solve it?

./pope
 
Lasse Koskela
author
Posts: 11962
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Ali Pope:
Very interesting problem Lasse. How do you really solve it?


It's really quite trivial. You just make the request processing thread (the one that enters your servlet class) create some kind of a "job order" for a different thread to perform. The decision between different ways of handling this passing of control is slightly more interesting, though, as you have to choose between (these are just the common cases I came up with) using plain JMS, JMS and a Message Driven Bean, or a custom thread pool. In all of these cases, you just need to agree how the request processing thread and the worker thread will communicate.

One rather easy way to do this would be something like this:
1. Servlet parses the request, figures out what the user wants to do, and creates a Job object describing the requested computation.
2. Servlet sends the Job object to a JMS queue and forwards the web browser to a "waiting" page, which refreshes automatically every 10 seconds to check on the status of the job.
3. In the background, one of the MDBs you've configured to listen to the JMS queue will pick up the Job object from the queue and begin computing the result.
4. Servlet receives an "is it ready yet?" request from the browser, looks at the database where the results will be posted, realizes that the job is still ongoing, and sends back another "waiting" page.
5. MDB is still processing.
6. Servlet receives an "is it ready yet?" request from the browser, looks at the database where the results will be posted, realizes that the job is still ongoing, and sends back another "waiting" page.
7. MDB is still processing.
8. Servlet receives an "is it ready yet?" request from the browser, looks at the database where the results will be posted, realizes that the job is still ongoing, and sends back another "waiting" page.
9. MDB is still processing.
10. MDB finishes processing and stores the result to the database.
11. Servlet receives an "is it ready yet?" request from the browser, looks at the database where the results will be posted, realizes that the job is finally done, and generates a "here's your result" page for the browser.
 
Lasse Koskela
author
Posts: 11962
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Lasse Koskela:
One rather easy way to do this would be something like this...


Obviously a lot easier solution would be possible in a single-JVM setting where you could rely on shared memory along the lines of:

1. request processing thread stores the Job to a synchronized FIFO queue -- Collections.synchronizedList(new ArrayList()) would do for starters -- and sends a "waiting" page to the browser.
2. the worker threads would pick up Job objects from the queue as fast as they can process them and would store the results into a Map.
3. another request processing thread sees the result in the Map and generates the "results" page.

This is pretty much the low-tech end of the scale. The other end, the more exciting end, is full-blown parallel computing systems like Google.
 
Eusebio Floriano
Ranch Hand
Posts: 241
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thx for the examples Lasse and Stan,
The exact point is:

Originally posted by Stan James:
But life is not all EJB, thank goodness.



Regards,
 
I need a new interior decorator. This tiny ad just painted every room in my house purple.
a bit of art, as a gift, the permaculture playing cards
https://gardener-gift.com
reply
    Bookmark Topic Watch Topic
  • New Topic