But is it? Read points 1, 3, 4 and 5 in my posting above.Originally posted by HS Thomas:
I accept implementing WeakHashMap is infinitely less complex.
Peter den Haan | peterdenhaan.com | quantum computing specialist, Objectivity Ltd
I accept implementing WeakHashMap is infinitely less complex.
but what it demonstrates is that WeakHashMap is not as straightworward as you might think.
1. Rejig your design to employ a counterintuitive mapping of clients to locks rather than, as the problem dictates, the other way around; ensure that your code enforces the one-owner-only nature of the locks that the data structure now no longer enforces.
2. Document in your design decisions why the resultant restrictions in the lock() API were a good tradeoff. Please note that the Data javadoc implies no restrictions whatsoever on the number of records locked.
3. Dig in the spec and API to understand how WeakHashMap works and how it affects your design. The difference between soft, weak and phantom references, anyone? Don't forget to go through the RMI specification to ensure that you are guaranteed that RMI releases all references to your object. After all, you cannot rely on the behaviour of the particular RMI implementation you happen to be using.
4. Carefully go over your code to ensure that the fact that WeakHashMap doesn't obey the usual Map invariants doesn't affect your code. For example, a call to map.containsKey(client) can return true yet a subsequent call to map.get(client) return null, even inside a synchronized block. This kind of odd behaviour may not affect you, but are you sure without fully understanding how WeakHashMap works and carefully inspecting your code? WeakHashMap is most emphatically not a simple, devoid-of-complexity drop-in replacement for HashMap as suggested.
5. Find a way make sure that locks are actually cleaned up timely and notifyAll() gets called so a freed lock can actually find a new owner within a predictable, bounded time. I haven't seen any answer to this yet.
And looking at the properties and constraints of the problem (funcionality, mathematical, performance, whatever slant you take on it), a WeakHashMap forces you to map exactly the wrong way around. It makes the code much less expressive of the problem, and introduces performance penalties arbitrary constraints in the process, without justification (in my eyes) in simplicity, functionality, or performance. I've already beaten this subject to death; I won't repeat that here.
quote:
--------------------------------------------------------------------------------
In the airline industry, in which I have actually worked, there are usually business rules that disallow a Client Rep from booking more then a single flight at a time
--------------------------------------------------------------------------------
I believe it is an absolute mistake to look at the database as a flights database.
The Data class you've been given makes this clear in no uncertain terms. Whoever wrote it went the extra (air) mile or two to make it absolutely, completely generic with zero, zip, zilch FBN dependence or FBN-specific assumptions. This is the class we are being asked to modify and enhance.
quote:
--------------------------------------------------------------------------------
There are absolutely no significant performance overheads, since we're talking about millisecond here, and in some cases, hashmaps are actually faster then treemaps
--------------------------------------------------------------------------------
They obviously are faster, as hashing gives you O(1) performance on most things where trees give you O(log N) performance. But we aren't comparing HashMaps to TreeMaps. We're comparing HashMaps to WeakHashMaps; AFAIK both have the same big-O behavior. WeakHashMaps are slower but, as you note, it does not really matter much -- we've not been asked to develop for performance -- it's just another very small and minor exhibit in the case against a WeakHashMap.
It's not even a minor issue. It's a minor, minor, minor issue. And since it is so minor, I'm sure that you have better arguments?
You're suggesting complexity where there isn't any.
I'm making the point, as you are below, the there is more complexity that has to be waded through before you can 'safely' use this feature.
that is, be notified of the loss of the client's reference. As you say, "The unrefernced interface is a standard solution if you need to remote object to do something before it exits.". : we want the remote object to
Yes, but there are more elegant ways of doing this then hand writing specific code for it. It's always best to have object cleaned up automatically if possible. Your suggestion ignores this.
-- well, that's exactly what we want to do: we want the remote object to clean up its locks. I think you generally will have to come up with very compelling reasons indeed to ignore standard API in favor of your own thing.
Not I, but you need to come up for a very compelling reason for ignoring the standard API. You want to ignore a standard object from the java.util package, in favor of a standard object from the java.rmi package. Since RMI implies more complexity then java.lang., then burden of proof lies with you, I would think.
Note that the requirements explicitly state that you should use standard API wherever possible.
It is my guiding light.
You do of course say that WeakHashMap is also standard API. Which it is, but not for this task.
I wasn't aware that parts of the java API were being ruled out for this task: was that in the documentation?. But seriously, of course it's standard API. The fact that you acknowledge this doesn't mean that it can be dismissed.
quote:
--------------------------------------------------------------------------------
Actually, in a remote system, just as in a local system, it's best to automatically delete objects that don't have a reference. If you can get that for free by using a WeakHashmap, in addition to releasing the locks those objects hold, then using Unreferenced seems, to me, not to be the most elegant solution.
--------------------------------------------------------------------------------
If you can get it for free, yes
And you can get it for free.
... but not at the expense of expressivity of the problem domain, arbitrary restrictions on server functionality,
Let's be clear: these restrictions are no more arbitrary then providing 'features' that the user didn't ask for, like multiple locks per client.
use of non-standard API,
This is just nonsense, The java.util.* package is a part of the standard API, as you yourself have attested on numerous occasions.
conceptual complexity,
Which is why I'm avoid the more complex solution.
cleanup predictability (gc involved), l
There is always clean up unpredictability, and the gc is always involved when you're working with RemoteObjects.
lock availability after cleanup (no notifyAll!),
You'll have to be more clear here.
and performance.
again, an explicit non issue, per the requirements. Also, it's never been established the performance is an issue with WeakHashMaps, though we have discussed it in the past. And before we go off on this tangent, lets stay on topic and focus the discussion. We can always address this later.
But all of the below instead:
1. Rejig your design to employ a counterintuitive mapping of clients to locks rather than, as the problem dictates, the other way around;
again, the problem never states this, nor does it 'dictate' it. The fact that you say that it does simply does not change that.
ensure that your code enforces the one-owner-only nature of the locks that the data structure now no longer enforces.
when the user provides no preference, then the developer has the responsibility to write the simplest implantation possible. IMO, yours is not that, for the reasons given above.
2. Document in your design decisions why the resultant restrictions in the lock() API were a good tradeoff. Please note that the Data javadoc implies no restrictions whatsoever on the number of records locked.
True. Thus, it's perfectly reasonable for a developer to provide the simplest solution, given this lack of restrictiveness.
3. Dig in the spec and API to understand how WeakHashMap works and how it affects your design.
Yes, you do need to understand how collections work.
The difference between soft, weak and phantom references, anyone?
versus all of the myriad of issues that can arise from Unreference? I'll take the standard API stuff, thanks. I still haven't seen a fully implements solution for the UnReferenced material.
Don't forget to go through the RMI specification to ensure that you are guaranteed that RMI releases all references to your object. After all, you cannot rely on the behavior of the particular RMI implementation you happen to be using.
Non sequitur, since you have to understand RMI regardless. The question is, are you also going to have to dig into the complexities of Unreferenced? Not in the design that I'm advocating.
4. Carefully go over your code to ensure that the fact that WeakHashMap doesn't obey the usual Map invariants doesn't affect your code.
Yes, you will have to understand the WeakHashMap in order to use them. Personally, I think they are worth the investment.
For example, a call to map.containsKey(client) can return true yet a subsequent call to map.get(client) return null, even inside a synchronized block.
Yes, this is a class example, and a well known one. However, has no bearing on this solution, as I think you know.
This kind of odd behavior may not affect you, but are you sure without fully understanding how WeakHashMap works and carefully inspecting your code?
WeakHashMap is most emphatically not a simple, devoid-of-complexity drop-in replacement for HashMap as suggested.
I think we're agreed that you would have to know how WeakHapMaps work before you used them.
5. Find a way make sure that locks are actually cleaned up timely
This is not guaranteed with either approach: like the gc, the JVM will simply decide when to call Unreferenced on a Remote object. But I'm sure you know this.
and notifyAll() gets called so a freed lock can actually find a new owner within a predictable, bounded time. I haven't seen any answer to this yet.
And I haven't seen this question articulated yet. Can you provide some context? Can you demonstrate a example where such a problem exists with a WeakHashMap?
M
Originally posted by Eugene Kononov:
What do you think is the output? Even if you are a senior developer, you would bet $100 that the "myValue" entry would be dereferenced after the key was set to null, right?
main argument against the WeakHashmap is that it is an artificial construct in the context of RMI garbage collection, as opposed to a natural API for Unreferenced. If I were to weigh the pros and cons of the WeaksHashMap vs Unreferenced, I would assign a very heavy weight to the "How natural?" criteria.
This assertion and the ones Peter made does throw the X against WeakHashMap for "Will always be dereferenced" off the chart.
I was wondering whether in the context of the assignment it will be allowable?
Thus, I might assign a .5 weight to the locking multiple locks to multiple clients issue, and probably a 3 or a 4 to the complexity and maintainability issue. That is, the WeakHashMap solution does not require any complex coding(or any @ all, just change your data structure).
Originally posted by Mark Spritzler:
Picture of Mark in the stands
This is so far off the mark that I hardly know where to start. I've shown that the problem is a natural fit for a Map in one direction only. Your assertion that it was a non sequitur has never become anything more than that, an unsubstantiated assertion. I've done my bit and went to the extent of using a mathematical argument. Now do your bit. Show me the gap. Show me where I miss the mark. Demonstrate. Don't just assert. Repeat it for my sake. Or link me the thread.Originally posted by Max Habibi:
However, I will say that simply invoking Mathematics, performance, etc., then making a statement them does not construes an argument.
Of course it doesn't say that you need to make it generic, because it bloody well already is fully generic! Are you trying to argue that that fact is wholly irrelevant? A coincidence? A matter not to be considered at all when contemplating modifications to and extension of that class? If so, you have a very strange approach to software development indeed.[...] Sun's design specs specifically state that you may choose to modify the Data class. They also say that you may choose to extend it. nowhere do they say that you need to make it generic: thus, it naturally follows that is not an absolute mistake.
That's a bit too easy and rather meaningless. Your position, taken to its logical conclusion, means that all code one could possibly write satisfies Sun's requirement that "the design should use standard Java package facilities wherever possible" because all code ultimately uses java.lang.* and so forth. That is of course not what it means. What it means is that, given a task, if there is a standard API to accomplish that task, you should use it. The standard API for doing something when all references to a Remote object get lost is Unreferenced. Fact. You're ignoring this in favour of your own solution which involves garbage collector action on Remote objects. Fact.Not I, but you need to come up for a very compelling reason for ignoring the standard API. You want to ignore a standard object from the java.util package, in favor of a standard object from the java.rmi package.
There is no gc involved in Unreferenced when a client crashes. Just the RMI DGC, and its action (in contrast to the JVM gc) is fully predictable here.There is always clean up unpredictability, and the gc is always involved when you're working with RemoteObjects.
After a client has been removed from the WeakHashMap, who is calling notifyAll() so that another client that may be waiting for the lock gets woken up? If my mental image of your code is correct, it doesn't happen. You have to wait an indefinite amount of time until a totally unrelated unlock event happens. If you happen to be working late and there's nobody else around, tough, you'll be around 'till the morning.lock availability after cleanup (no notifyAll!),
You'll have to be more clear here.
I wasn't referring to WeakHashMap performance, but to the performance penalty you pay for having the Map the wrong way around (lock() has O(N) performance rather than O(1)). No, performance is not a primary design goal, and not the most important part of the price to pay.again, [performance is] an explicit non issue, per the requirements. Also, it's never been established the performance is an issue with WeakHashMaps, though we have discussed it in the past.
Another glib comeback. Very good. WeakHashMap is not just another Map and you know it.Dig in the spec and API to understand how WeakHashMap works and how it affects your design.
Yes, you do need to understand how collections work![]()
If you have to understand how RMI works regardless, then you know how Unreferenced works, don't you? Because it's part and parcel of the RMI cleanup process that your WeakHashMap solution also relies upon, and (once you understand RMI DGC) a very straightforward part at that. Sequitur.Non sequitur, since you have to understand RMI regardless. The question is, are you also going to have to dig into the complexities of Unreferenced? Not in the design that I'm advocating.
No, I don't know this. The RMI DGC does not operate like the JVM garbage collector at all, and comes with more guarantees. Specifically, when a client crashes you are guaranteed to be notified when the DGC lease expires (assuming no other references remain). A WeakHashMap-based solution piles the server-side gc on top of the DGC action, and adds its unpredictability to it.[Timely cleanup] is not guaranteed with either approach: like the gc, the JVM will simply decide when to call Unreferenced on a Remote object. But I'm sure you know this.
Peter den Haan | peterdenhaan.com | quantum computing specialist, Objectivity Ltd
Yes; a "data structures expressive of problem domain" item. This is a vital attribute of good design, and to my mind, an attribute that is sadly lacking if you have the Map the wrong way around.Originally posted by HS Thomas:
Have I missed anything that is glaringly lacking in the chart ?
Peter den Haan | peterdenhaan.com | quantum computing specialist, Objectivity Ltd
Code expressive of recurring problem , reusable with no or little change
"data structures expressive of problem domain"
Maybe it does; the interpretation of this criterion is up to you. In that case I'd argue that its weighting is way too low. Finding a good mapping from problem domain to object (or data) model is the cornerstone of good design.Originally posted by HS Thomas:
I thought "code expressive of recurring problem [...]" covered "data structures expressive of problem domain"
Peter den Haan | peterdenhaan.com | quantum computing specialist, Objectivity Ltd
. Who, me?enthusiastically conducted
The second thing I'm missing from the list is that, as far as I understand the code Max is talking about, the WeakHashMap solution implements cleanup only imperfectly since waiting clients aren't notifyAll()ed that the now-freed-up lock is available. This, plus the dependence on the action of the server-side garbage collector, adds a lot of unpredictability to the cleanup action.
- Peter
quote:
--------------------------------------------------------------------------------
[Timely cleanup] is not guaranteed with either approach: like the gc, the JVM will simply decide when to call Unreferenced on a Remote object. But I'm sure you know this.
--------------------------------------------------------------------------------
No, I don't know this. The RMI DGC does not operate like the JVM garbage collector at all, and comes with more guarantees. Specifically, when a client crashes you are guaranteed to be notified when the DGC lease expires (assuming no other references remain). A WeakHashMap-based solution piles the server-side gc on top of the DGC action, and adds its unpredictability to it.
- Peter
Yes my banner is upside down, but with the characters available on the keyboard, that was the only direction I could use.
Mark
Correspondingly, since allowing a single client to lock multiple records is not required at all by the assignment, it ranks pretty low for me. I certainly wouldn't consider it mission critical. Thus, I might assign a .5 weight to the locking multiple locks to multiple clients issue, and probably a 3 or a 4 to the complexity and maintainability issue. That is, the WeakHashMap solution does not require any complex coding(or any @ all, just change your data structure). Thus, it's a true 'freebie'.
With the unrefernced, you have to defend your design by saying: "look, I know this is more complex code, but it keeps lost records from killing the application". The assessor might decide that the added complexity is worthwhile in terms of performance(in this case, I mean overall system friendliness, not resource usage). However, they might decide that it is not. Thus, you might actually end up getting docked points for going the extra mile!
I think you mean lost clients."look, I know this is more complex code, but it keeps lost records from killing the application".
Originally posted by HS Thomas:
What kind of keyboard are you using ,Mark?
Impression of a tree -should have been a banner pointing the right way up.![]()
[ March 21, 2003: Message edited by: HS Thomas ]
[ March 21, 2003: Message edited by: HS Thomas ]
Originally posted by Max Habibi:
[QB]
I agree that this is a reasonable view, and it is your view. However, it's not the view of the graders. Most authors who focus on this issue agree on this. From Kathy's book,
"But we do know one thing: They(The Assessors) aren't looking to see how clever an algorithm designer you are! If anything, it's just the opposite. Instead, think team player. And don't even think about showing off your programming prowess by revising the specification to do something even better and cooler than what was asked for...".
I can only reaffirm that it's a very serious minus to the project that you're adding complexity, and, IMO, the chart should reflect this.
Add to that the fact that you're doing so(IMO) in a complex and heavy handed way, to provide functionality that is not required: I doubt it will do any benefit for your score, regardless of whether you and or I feel that it should(and btw, I agree with you: I wish the accessors would reconsider this policy). specificity, you're adding two items of complexity.
The first is the you're architecturing the project to support a locks of multiple records per client, which is not specified, not used, and thus not necessary. The second that you're adding significant complexity for the sake of releasing records held by lost clients.
In the design that I suggested, the second feature add is a freebie in terms of coding complexity, because you simple use one type of map instead of another. That's all. The first complexity mentioned, supporting multiple locks per client, is also not added in this design. Thus, there is no net add of complexity required (except that programmers know the java.util package),and you're adhearing to the design specs, and you're providing a cool feature(free of charge in terms of LOC, function Points, etc). Hence, my suggestion.
All best,
M, author
The Sun Certified Java Developer Exam with J2SE 1.4
"But we do know one thing: They(The Assessors) aren't looking to see how clever an algorithm designer you are!
I can only reaffirm that it's a very serious minus to the project that you're adding complexity, and, IMO, the chart should reflect this.
------------------------------
"But we do know one thing: They(The Assessors) aren't looking to see how clever an algorithm designer you are!
-------------------------------------------------------------------------------
Well, shouldn't this be a good argument against the WeakHashMap?
I consider the WeakHashMap, not the Unreferenced, to be a clever idea to deal with garbage collection.
Yes, absolutely. But again, for the reasons that I already listed in my previous post, I consider HashMap a more complex solution.
It seems that we are going in circles in this thread. The opposing sides presented their arguments and stand firm by them.
I have taken Max's comments into consideration and given WeakHashMap a score for Less complexity.
and also added a score for Unreference for going
"that extra mile" under "provides for future capabilities of the system by shortening the learning curve".
It's difficult to see how much further this can proceed / be resolved.
I think there has been some agreement where issues have been weighed in the balance.Thanks, Max,Peter,Mark ,Eugene for letting me participate in the debate in this way. Hope I haven't ruffled too many feathers..
If you had a team of developers with a multitude of skills but with no RMI/distributed computing knowledge you may NOT want them to pull them down this road if the business wasn't ready yet.Their skills may be better used elsewhere for the good of all -perhaps that is what the assessors mean by team player.
I discover that near as I can tell, the fabulous feature of the system where a single transaction can have debits from several accounts and credits to several accounts simply isn't used. Each transaction comes from one account and goes into one account. Is it possible to simply the system as in figure 2?...I ask Massimo to come sit with me while I examine the system...every single one of them( the transactions) has a single debit and a single credit account
But if a business opportunity came along that required this knowledge, then the developers with this knowledge ,would come in handy. Pity if you had no developers on hand to help.
If I were a developer I would try to get this skill, just in case. There certainly will be more opportunities in time
and the assessors may change the way they mark this criteria.
I've done my bit and went to the extent of using a mathematical argument.
Now do your bit. Show me the gap. Show me where I miss the mark. Demonstrate. Don't just assert. Repeat it for my sake. Or link me the thread.
Of course it doesn't say that you need to make it generic, because it bloody well already is fully generic!
Are you trying to argue that fact is wholly irrelevant? A coincidence? A matter not to be considered at all when contemplating modifications to and extension of that class?
If so, you have a very strange approach to software development indeed.
quote:
--------------------------------------------------------------------------------
Not I, but you need to come up for a very compelling reason for ignoring the standard API. You want to ignore a standard object from the java.util package, in favor of a standard object from the java.rmi package.
--------------------------------------------------------------------------------
That's a bit too easy
and rather meaningless. Your position, taken to its logical conclusion, means that all code one could possibly write satisfies Sun's requirement that "the design should use standard Java package facilities wherever possible" because all code ultimately uses java.lang.* and so forth.
What it means is that, given a task, if there is a standard API to accomplish that task, you should use it. The standard API for doing something when all references to a Remote object get lost is Unreferenced. Fact.
You're ignoring this in favour of your own solution which involves garbage collector action on Remote objects. Fact.
This piece of rhetoric is not what I'd expect of you, Peter. By all means, discount aesthetics and elegance as purely subjective. That leaves the core of the argument, which was all about avoiding complexity in your algorithm. This is strictly measurable, and deliver real business benefits (or the lack thereof).
![]()
There is no gc involved in Unreferenced when a client crashes. Just the RMI DGC, and its action (in contrast to the JVM gc) is fully predictable here.
As a matter of fact, the gc is involved, because RMI needs to hold it off until the object has Unreferenced called on it, so the above is simply untrue. After a client crashes, the RMI DGC needs to get kicked of, which is does so, whimsically at the hands of the JVM. The DGC is fully analogous to the GC in this case, as shown here on the Sun site.
Distributed Garbage Collection: RMI uses its distributed garbage collection feature to collect remote server objects that are no longer referenced by any clients in the network. Analogous to garbage collection inside a Java Virtual Machine, distributed garbage collection lets you define server objects as needed, knowing that they will be removed when they no longer need to be accessible by clients.
At some point after that, after all references to the client have been released, the remote object has Unreferenced called on it, if the server runs for long enough. When this call occurs is whimsical in nature, and in the hands of the JVM. It also contains a great deal of complexity, and subtly, per the sun documentation.
"Many subtleties exist in the protocol; most of these are related to maintaining the ordering of referenced and Unreferenced messages in order to ensure that the object is not prematurely collected."
that you had better be prepared to answer for in your design doc, to explain that the object is not garbage collected while Unreferenced being called on it.
Thus, you have two levels of whimsy with Unreferenced: When the DMG is called to decrement the reference count, and when the server's JVM decides to call the Unreferenced method on the remoteObject.
The sun spec even specifically states that
remote references cannot guarantee referential integrity
deterministic behavior indeed.
After a client has been removed from the WeakHashMap, who is calling notifyAll() so that another client that may be waiting for the lock gets woken up? If my mental image of your code is correct, it doesn't happen. You have to wait an indefinite amount of time until a totally unrelated unlock event happens. If you happen to be working late and there's nobody else around, tough, you'll be around 'till the morning.
As you do with any sort of unpredictable process, including the calls to Unreferenced and the DMG. Surely you're not trying to imply that is a unique experience? The exact same issue pops up if a remote client crashes(or doesn't crash, but it's been so silent the server believes it crashed). You have to wait an indeterminate length of time before the DMG decides to collect that client's remote Object. Remember, the DGC is analogous to the gc in this context. But of course, you know this.
I wasn't referring to WeakHashMap performance, but to the performance penalty you pay for having the Map the wrong way around (lock() has O(N) performance rather than O(1)). No, performance is not a primary design goal, and not the most important part of the price to pay.
And I'm not going to correct you again, about the assumption that the map is the 'wrong way around' when it fits the requirements to a T, nor that you're avoiding unlock. Let's just agree the searching performance is a non-issue, and only bring it up again if we're tied, and need a tie breaker. Ok?
Another glib comeback.
Peter, have a sense of humor. And no, this wasn't particular glib. For glib, see above where I used your own reaction![]()
Very good. WeakHashMap is not just another Map and you know it.
I do know it: and you better know that it too, if you want to be a developer and not a programmer(I don't mean you per se: I'm sure you're a fine developer). I don't think it's a particularly difficult concept. WeakHashMaps don't add to the reference count of the value. I don't find that particularly hard. As a matter of fact, I find it elegant.
If you have to understand how RMI works regardless, then you know how Unreferenced works, don't you?
Are you implying that understanding Unreferenced is a requirement to using general RMI services?
Because it's part and parcel of the RMI cleanup process that your WeakHashMap solution also relies upon, and (once you understand RMI DGC) a very straightforward part at that.
Are you saying that Unreferenced is part of the WeakHashMap solution? If so, I'm afraid I've failed to explain it to you properly. My apologies.
Sequitur.
Not at all. If you suggesting that the WeakHashMap needs the Unreferenced interface, then you are simply misinformed. If you are not saying that WeakHashMap requires Unreferenced, then it's a non sequitur.
No, I don't know this. The RMI DGC does not operate like the JVM garbage collector at all,
Demonstrably false.
From the sun spec:
Analogous to garbage collection inside a Java Virtual Machine, distributed garbage collection lets you define server objects as needed, knowing that they will be removed when they no longer need to be accessible by clients.
It does not operate like the JVM garbage collector at all? Please.
Specifically, when a client crashes you are guaranteed to be notified when the DGC lease expires (assuming no other references remain). A WeakHashMap-based solution piles the server-side gc on top of the DGC action, and adds its unpredictability to it.
Again, a fuss is being made over a non issue. It's either the GC on top of the DMG, or Unreferenced on top of the DMG, then followed by GC. The WeakHashMap simply cuts out the middle man, and avoid one last call to the RemoteObject.
HTH,
M, author
The Sun Certified Java Developer Exam with J2SE 1.4
[ March 22, 2003: Message edited by: Max Habibi ]
Originally posted by Mark Spritzler:
What I think we have accomplished here, is a place for future SCJD goers to read about both sides and to make their own choice. There can't be a specific winner here, since they both have great merits and of course opinions.
Thank you everyone for participating and stating great arguments. We are all better for it.
Mark
There are still relevant issues to be discussed, I think. Don't tell we we're boring you .
Every snowflake is perfect and unique. And every snowflake contains a very tiny ad.
Smokeless wood heat with a rocket mass heater
https://woodheat.net
|