-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve the usage of Sysfs in the interaction with EV3Dev #765
Comments
I don't think we should try for concurrency safety or state inside Sysfs. I don't think we can do it correctly and do it well at the same time; I think we'd regret any choice internal to Sysfs. I think the best path forward is to step away from Sysfs, and start using DataChannelRereaders and (new) DataChannelRewriters directly in Battery, GenericMode and BaseRegulatedMotor (contains EV3LargeRegulatedMotor's interesting code). (That's what I did for my kid's basic control loop.) The nice thing about Battery is that it only needs 2 readers, zero writers. BaseRegulatedMotor needed 4 readers and 5 writers. The GyroSensor had 1 writer and 3 readers. I should have some free evenings this week after the kids are in bed. I could do a PR of DataChannelRereader in Battery - and make it Closable - as a first step if that's of interest. As a second step I can do a PR for a DataChannelRewriter. |
Let’s try your idea. Apart of the performance, the first goal in this issue, I am pretty interesting to introduce Mockito. In the project we added tests (One innovation in LEJOS ecosystem), but it is possible to improve. Idea with a Try for resources + Autocloseable: try (DataChannelRereader rereader = new DataChannelRereader(filePath)) {
String result = rereader.readString();
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("value: {}", result);
}
return result;
} Currently, the library mount a temporary file system, but using Mockito, the developer could follow: @Test
public void getEV3BatteryVoltageTest() throws Exception {
final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.EV3BRICK);
Battery battery = Battery.getInstance();
LOGGER.info("{}", battery.getVoltage());
assertThat(battery.getVoltage(),
is(Float.parseFloat(FakeBattery.BATTERY_FIELD_VOLTAGE_VALUE)/1000000f));
} Review if I will find an easy example which it writes to incorporate to the test bank before touch the motors. |
For
Does that file path exist on every platform (so the code can open the file) or is it missing from PISTORMS and BRICKPI ? |
Also, most of the codebase looks like it is after the " |
For Mockito - slipping an alternate filesystem in place is pretty easy. Does Mockito add value to that approach? (I think it at least adds a little.) |
Hi @dwalend,
Exactly, this particular method is only available in EV3 Brick.
Interesting point for a Debate. Currently I return a bad value and I advice in logs?
|
PR to follow in a moment. |
Here's the PR: #770 |
Hmm. Instead of using -1f as the bad value try out Float.NaN . I think in Java Runtime Exceptions are a better answer, but at least NaN will blow up if someone tries to multiply or add it. I'm coming back to Java from Scala. I didn't miss the caught Exceptions. Java's the only language that has them I think. They put you into a contravariant world. Really if you're using the wrong robot then the Battery class isn't going to do what you'd hoped and there's no saving that part of the program. |
Here's a draft DataChannelRewriter : https://github.com/dwalend/ev3dev-lang-java/pull/1/files |
I have merged the changes. The new version maintains a good readability. I see your strategy, with this kind of inmutable objects will increase the performance, maintaining the connection Open.
Yes, please, change the code to return a Runtime Exception. The developer will fail once and reading the logs, he/she will understand the issue.
For EV3DevDevice, it provides some objects like ev3DevProperties Power provides the contract for Battery. To test the new class: DataChannelRewriter, I believe that the class EV3Led could be a good candidate:
We should have a Unit Test for the class: DataChannelRereader.java Can you try to Mock: DataChannelRereader from BatteryTest? |
Unfortunately, DataChannelRereader is currently not immutable. The problem is with the cached There is also a caveat with the cached FileChannel that I didn't realize earlier: https://stackoverflow.com/a/53361005/1561345 (state/current file pointer is visible across threads and could cause problems too). I am hoping this could be fixed by using a The two above fields mean that methods using DataChannelRereader (one instance of it) are currently safely callable only from one thread at a time. My intuition is getting OK with that approach, but I'd add a warning to Javadoc for that method about that. For example, |
Hi @JakubVanek, the class is Immutable:
https://en.wikipedia.org/wiki/Immutable_object other stuff is the I put some links as a reference:
Kudos for @JakubVanek |
I think the non-thread-safe nature is intentional (thread safety decisions are shifted to the user). I just consider this unfortunate with singletons (only one thread can access Battery at a time, or an additional locking wrapper has to be used by users). It may also change semantics of programs that were built with the assumption that multithreaded access is OK (this might be only an imagined problem though, I have no statistics about how the API is really used). EDIT: I have realized that sensors and motors have a similar problem. My initial thought about how the multithreading safety could be implemented on the user side was to manually dedicate one object to each thread. However, this is currently not possible either due to #740. Therefore, it seems that the user-side solution is to either to use local locks or to design the application in a way that doesn't require concurrent multithreaded access at all. |
No, ByteBuffer has a known bug and It was solved in |
This comment has been minimized.
This comment has been minimized.
This could work, but as the issue notes, the intent is likely the following:
|
Sorry, @JakubVanek Please, Stop doing it. I am not going to say the same stuff every week. If you need one issue to collaborate, tell it explicitly but respect the activity from others. |
I am sorry for posting another comment (this time it is related directly to the threading issue). One way out would be to declare the whole library as safe to access only from one thread (IIUC Swing does this similarly, it has its own UI thread and changes can be safely made only from within it). Under that assumption the changes in Battery & DataChannelRereader should work fine and indeed improve performance. However, I'd consider this an API contract change and as such it should be documented and mentioned in the release notes. |
Listen me @JakubVanek, Don´t say sorry, your ideas are valuable in many many cases, but you need to measure your energy. Review the backlog, exist new good issues to develop new stuff from scratch. Reply one issue to get the ownership. Indeed, Power interface will have more relevance in the future. Enjoy Good night |
Do you two think it's worth making the read() and close() methods in DataChannelRereader synchronized ? (Anything Closable has at least two states.) That'd give the thread safety. The JVM is pretty good at optimizing synchronized away if only one thread uses a lock. I personally think it is reasonable to just mention that the calling code is responsible for concurrent access as a caveat in the docs that this code works like most of the rest of the Java ecosystem. Ultimately there's no reasonable way to prevent two separate processes from writing the same file. We could only ever fix so much of the problem adding locks to a library. We could always measure performance and let that guide us. Crazy day today. I'll slip these changes in - likely tomorrow evening, and start in on the LEDs. |
Hi @dwalend,
At the moment, I don´t have a conclusion. This morning, I was thinking in some scenarios and I coded a TestCase to review the concurrency for the new class: The testcases are: /**
* Writer1 -> pairs.txt
* Reader1 <- pairs.txt
* Reader2 <- pairs.txt
*
* Writer1 -> odds.txt
* Reader1 <- odds.txt
* Reader2 <- odds.txt
*/ In this is case, is not very realistic, When I execute multiple times, I receive the following exception in some case:
From the block: do {
byteBuffer.clear();
channel.position(0);
n = channel.read(byteBuffer);
if (n == -1) {
throw new IOException("Premature end of file ");
}
} while (n <= 0); In the second test scenario, more realistic in my opinon: /**
* Writer1 -> pairs.txt
* Reader1 <- pairs.txt
*
* Writer1 -> odds.txt
* Reader1 <- odds.txt
*/
Why appear that exception?
Before continuing with LEDs, we should close the discussion about this exception. Juan Antonio |
@jabrena I have quickly looked at @dwalend Locking doesn't seem strictly necessary. If the buffer is moved to a thread local variable and the file channel is accessed atomically (i.e. only one function call with absolute positioning), the race can be avoided (I think, I may be wrong). I have provided #775 with an overview how that could look. I haven't thought about concurrent writers, the issue I was thinking about is with concurrent readers. There is a vulnerable time window between the moment the NIO read is completed (and data are stored in the buffer) and the moment the data from the buffer are converted to a string. There is also another window between the moment the buffer limits are cleared and the rest of the code. The scenario that is "haunting" me is that someone tries to read battery voltage from multiple threads (whatever may their motivation be) and they occasionally get an incorrect value and/or exception due to unparseable integer (empty string). |
Will https://github.com/ev3dev-lang-java/ev3dev-lang-java/pull/781/files#diff-c0e0309e22ce34fb394a33b423bdafcf7785541728b2b6b6ef21490144a47b38 do? I'm afraid I might have lost the thread. |
I will review later the link. Basically, It is pending a Unit Test class for DataChannelRereader |
Hi @dwalend, I saw the test. I will review tomorrow. Cheers |
@dwalend |
Hi @dwalend, Can you review the Unit tests about EV3Led, many tests failing now.
But if you compare with the previous implementation, they are running fine:
@JakubVanek Thanks for the comments in the PR, but it is not necessary. Remember for the next time. |
I was thinking about the testing model and how to improve it. Currently in
In the constructor, we initialise some Readers, but if that readers come from a Take a look this article: Juan Antonio |
It was missed in the PR review that I didn´t find a Unit Test for the class |
@jabrena - that's why I've asked for some help with the test infrastructure. Those files need to be in place earlier in the tests - |
No worries, Tomorrow, I will review in detail the tests and code, in order to fix it and I will do later an explanation here. Tomorrow, I will continue. Juan Antonio |
Hi @dwalend I am working in the package It will take few hours |
I found the issue: In the support for EV3Led, the keys to get access to ev3 leds are absolutes:
And later, if you execute the tests, EV3Led call a path that doesn´t exist in local. if (direction == Direction.LEFT) {
redWriter = new DataChannelRewriter(ev3DevProperties.getProperty("ev3.led.left.red"));
greenWriter = new DataChannelRewriter(ev3DevProperties.getProperty("ev3.led.left.green"));
} else {
redWriter = new DataChannelRewriter(ev3DevProperties.getProperty("ev3.led.right.red"));
greenWriter = new DataChannelRewriter(ev3DevProperties.getProperty("ev3.led.right.green"));
}
This is the reason that in other parts of the project, exist this utility: EV3DevFileSystem.getRootPath(); One example using the technique is here: In the Brick, the method will return System.setProperty(EV3DevFileSystem.EV3DEV_TESTING_KEY, FakeBattery.EV3DEV_FAKE_SYSTEM_PATH); The method will return something like:
I will create a Fake EV3Led class for you tomorrow and I will review the tests. It is my fault, that part doesn´t have a good documentation. :) Review the following tests to see the style: When I fix the tests, I will tinker a way to Mock Juan Antonio |
Working in the tests. |
Hi @dwalend, The tests and Pipeline was fixed, please download the latest commits from the branch. You should not have any issue when you run in local the code:
I will explain one test from the Test class: with a test which follow a BDD Style: https://martinfowler.com/bliki/GivenWhenThen.html @Before
public void resetTest() throws IOException {
System.setProperty(EV3DevFileSystem.EV3DEV_TESTING_KEY, FakeBattery.EV3DEV_FAKE_SYSTEM_PATH);
FakeBattery.resetEV3DevInfrastructure();
new FakeBattery(EV3DevPlatform.EV3BRICK);
}
@Test
public void given_actuator_when_getDirection_then_Ok() throws Exception {
//Given
FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK);
//When
@SuppressWarnings("deprecation") EV3Led led = new EV3Led(EV3Led.RIGHT);
EV3Led led2 = new EV3Led(EV3Led.Direction.RIGHT);
//Then
EV3Led.Direction expectdedDirection = EV3Led.Direction.RIGHT;
then(led.getDirection()).isEqualTo(expectdedDirection);
then(led2.getDirection()).isEqualTo(expectdedDirection);
} In the beginning of the tests, we set a system property in order to indicate the library that we are in Testing Mode.
A fake object as
https://martinfowler.com/bliki/TestDouble.html Why in the beginning of the test, we use FakeBattery? Because we use the path from battery to identify the Brick (EV3, BrickPi or PiStorm) and this is the reason that I recreate a Battery with the minimum files to run the whole system:
When the test begin, we initialize a Fake Object to represent a Led: //Given
FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK);
Once the testing environment has a minimalistic representation to operate, you use your normal object in a natural way: LED led2 = new EV3Led(EV3Led.Direction.LEFT);
Maybe, I give some light about the way of testing. With that idea, You could use a FakeBattery to expose few files in the testing environment and use the indirection from
in order to include the missing tests for:
In Parallel, I will review how to mock previous Object in order to introduce Note: In the next week, I will upgrade the Java docs for the whole package Juan Antonio |
Thanks! I knew there had to be something like that in place somewhere, but didn't see where the file system slipped in. I would have never thought to look at NXTTemperatureSensorTest.java for an example. Sorry for going a few days without a response. Kid stuff over the weekend, then I opened a new project at work. Any reason to ever clean up things in an @after ? I guess my main criticisms are 1) that FakeBattery isn't a mock of the Battery class, but a mock of the battery as it appears in the file system. and 2) That's a lot of complexity of inheritance layers to mock what is presented as a file system. I think both of those are just style choices, provided we don't fall prey to the ossification it can cause. Should I go for the EV3LargeRegulatedMotor or the EV3GyroSensor next? EV3LargeRegulatedMotor's complexity is all packed into BaseRegulatedMotor . That'll sweep up EV3MediumRegulatedMotor and NXTRegulatedMotor in the same pass. My wayfinder branch has 4 Rereaders and 5 Rewriters, which seems like a lot. Also, I don't have an NXTRegulatedMotor to test. EV3GyroSensor inherits from BaseSensor which isn't so bad. I'd like to eventually add a Rereader and a Rewriter for the MODE and SENSOR_MODE, but that doesn't happen in my control loop. The complexity comes in via three GenericMode s . GenericMode needs an array of Rereaders, one per sample. That changes how 6 different sensors would work in one pass. One way around that would be to make specific modes for GyroSensor. That seems a natural path to follow, but would be an expansion of scope. What do you think makes sense for a next step? |
Good morning @dwalend
No worries. At the end, we are investing our spare time on it. We participate because we like it. :) Testing:
It is a new Test for a new Sensor added last week, so I implemented with the latest approach.
I will review that point this week.
At the end the whole model tries to replace the real behaviour of EV3Dev in order to be consumed by Java. What do you think makes sense for a next step?
But before adding that support, remember that it is missing the following tests:
Note: Review the following docs. I wrote them thinking in a classroom in mind. Review if you consider that you could reuse in your course. If you find something missing, we could improve them in the future. From my side, I will be working on: Have a good week and thanks for your contributions :) |
I added a test of DataChannelRewriter in #794 . What else would you like to see tested in DataChannelRereader ? |
Hi @dwalend, It would be nice to have the same kind of test developed for DataChannelRewriter in DataChannelRereader please |
@jabrena - it's been merged for some time: https://github.com/ev3dev-lang-java/ev3dev-lang-java/blob/sysfs_perf2/src/test/java/ev3dev/utils/DataChannelRereaderTest.java (You've improved it. ... It's not particularly memorable, a lot smaller than the rest of your cleanup.) |
Forget my last message... Sometimes I write in automatic mode... I will merge it today :) |
@JakubVanek @jabrena - what do you think of the approach in #795 ? I avoided changing GenericMode by putting a specific mode in EV3GyroSensor . The next step down that path would be two more specific modes inside of EV3GyroSensor, and probably a refactoring to get a generic "1 value" specific mode. |
@JakubVanek @jabrena - Juan asked me to show what the fix for EV3GyroSensor would be inside of GenericMode. It'd be #796 . What do you think of that one? I'm hesitant to make changes inside GenericMode. I think my main complaint is that it touches a lot more of the system. The interaction with NXTTemperatureSensor seems particularly problematic. The more I work inside the SampleProvider hierarchy the less comfortable I am with teaching it to kids. I'd much prefer to flatten it out to give specific APIs - for example - EV3GyroSensor a readAngle() method - and throw an exception if the sensor is in the wrong mode. That's a bigger change, for a different day, but much easier to explain and use. I'm much more comfortable with the path forward in #795 , which limits the change to EV3GyroSensor . (Just one mode of the three in that pull request so far.) |
Hi @dwalend,
This is issue is not to add new methods, the issue is to increase the performance with the new classes. I understand your motivation, but at the moment, it is not possible. I will fix today/tomorrow the failed tests. Upgrading GenericMode with the new approach, we upgrade many parts of the system, so it is not a problem. |
If we're to follow that same path for the motors then I can put together changes in BaseRegulatedMotor that will take care of both of the EV3 motors. That's 4 DataChannelRereader s and 5 DataChannelRewriter s in one go. That OK? |
Hi @dwalend, Ok, go ahead with that changes |
Hi @dwalend, Returning to the issue. and you? Juan Antonio |
In the previous Issue:
#748
We improved the performance with current architecture, the usage of
Sysfs
.Sysfs
in the current form, is a stateless class with static methods.Ideas to improve in this iteration:
setSysfs
toMock
Sysfs to improve testing. UsingGuava
, it could be possible to expose only for testing with the annotation@VisibleForTesting
but I don´t want to increase the weight of the jar in 2mb.@dwalend, what other ideas, do you have?
@JakubVanek, what other ideas, do you have?
In your opinion, what state is necessary to maintain in
Sysfs
and have some kind of concurrency protection?Criterias for PR:
sysfs_perf2
gradle checkstyleMain
JMH
We will analyse for the classes:
We will begin with Battery, an easy case and later, we will tackle a complex case like a Motor.
Juan Antonio
The text was updated successfully, but these errors were encountered: