This is more a curiosity than a real-world requirement. As an example to demonstrate what I mean by 'Controlling how an interface is implemented'..
Say I wanted to implement a new task scheduler, and I exposed ways for the user to enqueue new work. The work adheres to some interface IWork.DoWork
, or whatever. How can I ensure that the user doesn't spawn new threads within their implementation.
Keep in mind, this is just an example, I'm not actually trying to implement a thread scheduler. I'm just looking for ways to control how an interface is implemented, if that's possible. I'm interested in examples from any language.
Edit: To clarify, I'm looking for ways to restrict interfaces above and beyond what is normally allowed by function level interfaces. How do I implement IWork.DoAnythingExceptCreateNewThreads
?
4 Answers 4
Can you do that?
Providing a software interface is like offering a service to a customer. A customer can choose any service that is offered by anyone. For example, if library A provides a task scheduler and library B provides an easy way to spin up new threads, a customer may choose to use both.
Why would the customer want to do that? Can I persuade the customer not to?
The best ways to persuade the customer not to combine it with another service are:
- Offering the same (equivalent, or even better), service yourself,
- Explain to your customers why it is best not to combine it with another service, and hope that they understand
What if there is a legitimate technical concern that makes it not a good idea?
Explain that technical concern to customers in a way they can understand.
For example, one of the biggest threat to fixed-size thread pool implementation is that a worker function may invoke a Sleep
that either ties up the thread for some time, or a Lock
/ Acquire
(blocking wait) that can only be unlocked by another working thread. Consider a simple scenario that all 8 threads of a fixed 8-thread pool decides to lock. Since there are no other threads able to execute code to unlock it, these 8 threads are in a jam forever.
Thus, fixed thread pool implementations need to explain to the user that it is dangerous to use anything that may block inside worker functions. In particular, it is even more dangerous if the worker code tries to acquire any lock/monitor that can only be released by another worker thread.
Finally, introduce to the user several alternatives to locks that are found to be useful in computational workload.
- For example, results from each worker thread can be temporarily stored or appended to a concurrent list or dictionary, and combined at a later time.
- Likewise, a large enough array can be allocated so that each worker thread can write results to non-overlapping parts of the array without trampling each other's output areas.
- Shared immutable data do not need the protection of lock.
- And so on. (There are white papers from some concurrency libraries that teaches best practices like these.)
What if I am really interested in blocking the user's code trying to spawn new threads?
This falls into limiting privileges for the code being executed. Limiting privileges require runtime support from the execution environment. In other words, if the execution environment doesn't implement dropping of privileges while executing code, you cannot implement this feature yourself. At best you can put some documentation to persuade your users why it is bad idea to do so.
In general, if you want to take away user's freedom to use another service, you will have to find out that other service's provider (e.g. the runtime environment or the OS, or another piece of software), and then either ask that provider to block access (see: access control), or somehow interfere with that service (see: denial of service).
This older question on Stackoverflow might be useful. Though please don't hesitate searching for newer and more accurate answers.
Some execution environments provide very elaborate access controls:
(I am not familiar with these things so I cannot explain any further.)
- https://msdn.microsoft.com/en-us/library/system.security.permissions.codeaccesssecurityattribute(v=vs.110).aspx
- https://msdn.microsoft.com/en-us/library/system.security.codeaccesspermission(v=vs.110).aspx
Finally, you can execute user's code inside your own environment, which is different from the one your code executes on. This is called a Sandbox. The user's code is like a captive that lives inside a dungeon created by you.
An interface is a contract. The implementation needs to respect that contract if it implements the interface.
But this is just like with contracts in real life. You have a contract and someone else signs that contract. How do you make sure they respect the contract? Are you going to follow him non stop and make sure they do? for how long are you going to follow them?, etc.
And just like in real life contracts, if you brake the contract there are consequences (pay extra fees, pay fines, go to jail, etc).
The answer is you can't. The implementation can be anything the language allows you to do. It's up to the developer of the implementation to actually respect the contract.
If you want to have some sort of control, or restrict the implementation, or set some direction in which it should move, maybe use an abstract class with a template method instead of an interface.
-
This kind of side-steps the question. I'm looking for languages and/or patterns that allow framework developers more control over what the contract is. In my example, the contract is simply
IWork.DoWork
. What I'm looking for is how to create a contract likeIWork.DoAnythingExceptCreateNewThreads
ConditionRacer– ConditionRacer10/29/2016 20:46:33Commented Oct 29, 2016 at 20:46 -
@ConditionRacer: I'm not sure there are any. The closest thing that pops to my mind now is "design by contract" but I don't see that working either, especially with threads. I see this as you not trusting the implementers of your interface. If this is instead about warning the implementors, then what's wrong with adding very clear documentation to the interface that says "implementations should not start new threads"?Bogdan– Bogdan10/29/2016 20:55:28Commented Oct 29, 2016 at 20:55
-
1C# uses attributes (and Java uses annotations) to allow code to declare (promise) some sort of compliance, and allows the caller to inspect (search for) such declarations. However, enforcement of such restrictions are still subject to the factors which I explained in my answer.rwong– rwong10/29/2016 21:17:53Commented Oct 29, 2016 at 21:17
-
@rwong: "C# uses attributes (and Java uses annotations) to allow code to declare (promise) some sort of compliance, and allows the caller to inspect (search for) such declarations". I agree, but you have the same problem as you do with the interface itself. By implementing the interface you "declare (promise)" you will comply with its contract.The caller can then inspect if you did. It's the same thing. But it does not actually solve the problem because, well, how do you inspect? The implementation may start threads, may use an injected pool of threads, ...Bogdan– Bogdan10/30/2016 08:47:42Commented Oct 30, 2016 at 8:47
-
... may delegate to other objects that start threads, may use a native library to do it, etc. What do you look for to state with certainty that "yes", it's a good implementation? Pragmatically speaking your options are: (1) you clearly document how the interface should be implemented (i.e. don't start threads) with the implementor supporting the consequences if he fails to do so, or (2) don't provide the interface in the first place if you are worried by the consequences of a bad implementation.Bogdan– Bogdan10/30/2016 08:48:30Commented Oct 30, 2016 at 8:48
I believe this is possible in Haskell. In Haskell, spawning a new thread is an IO function. If you provide some interface in form of a function which takes a user-supplied function as input (the "do work" function), and you don't declare the type of this input function to be an IO-type, then the user is prevented from spawning threads (or indeed any other IO-operation) inside the worker function. A custom monad would allow you to control precisely which IO operations would be possible inside the supplied function.
In don't know of any OOP language which support similar constraints though, but Haskell shows it is at least theoretically possible.
What you are looking for is called "Design by contract". This approach extends the syntactical part of an interface definition by a semantical part, utilizing preconditions, postconditions and invariants.
For your example, the "invariant" you are looking for is the number of different threads behind "IWork.DoWork", wich should be always one. Of course, this is a little bit oversimplified, but I guess you get the general idea.
As you can see when looking into the Wikipedia article, programming language support for DBC differs from language to language. Some languages allow you to add formal contracts to the interface definitions - at least to some degree. Some other languages natively allow you only to specify contracts as part of the information interface documentation.
-
I would argue that the invariant is stronger than what you described. (1) it is to execute on the one thread which the scheduler has allocated to it; (2) it is not allowed to ask another thread to execute anything on its behalf; (3) it is not allowed to create new threads; (4) it is not allowed to tamper with any other threads, much less terminating any. Also, the invariant must be held true any time during the function call - it is not sufficient to just verify the invariant before and after the call. It can be understood as an example of DBC, though not effectively enforceable by DBC.rwong– rwong10/29/2016 23:12:16Commented Oct 29, 2016 at 23:12
-
"Some languages allow you to add formal contracts to the interface definitions - at least to some degree." Surely such formal contracts accommodated by language support and attached to interfaces speak to properties of the (elements of the) interface itself, not the implementation behind it in the abstract, no?Erik Eidt– Erik Eidt10/29/2016 23:16:23Commented Oct 29, 2016 at 23:16
-
@rwong: yep, I was oversimplifying for the sake of clarity, trying to answer the primary question of the OP, not going through all the details which are surely necessary to make this work with his example.Doc Brown– Doc Brown10/30/2016 07:23:16Commented Oct 30, 2016 at 7:23
-
@ErikEidt: I don't think the OP meant "to control how an interface is implemented" literally - the way I understand the question, the OP means "to control certain semantics or behaviour of a client implementing an interface". And that falls under the realm of DBC.Doc Brown– Doc Brown10/30/2016 07:30:02Commented Oct 30, 2016 at 7:30
Explore related questions
See similar questions with these tags.
DoWork
, wouldn't a DSL basically be a general purpose language?