Skip to content
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

Safe check rlimit syscall and refactoring #3801

Merged
merged 1 commit into from
Dec 5, 2017
Merged

Safe check rlimit syscall and refactoring #3801

merged 1 commit into from
Dec 5, 2017

Conversation

hoenirvili
Copy link
Contributor

@hoenirvili hoenirvili commented Mar 19, 2017

This PR contains:

  • this contains refactoring
  • this adds safe checking around rlimit syscall
  • more doc string

@Kubuxu
Copy link
Member

Kubuxu commented Mar 19, 2017

Ok, few things:

  • please split refactors, added tests and docs into separate comments.
  • if you are changing the rlimit logic please also change it for freebsd, or preferably extract the code that has to be different on freebsd vs rest (getting and setting the rlimit) and reuse the rest.
  • the dependency of yours is not available - please coordinate with me on IRC to pin it on our nodes
  • more in review comments

EDIT: I see that freebsd code is in sync, but it would be best to DRY it up as the logic got a bit more complex. It should be safe to use uint64 in general and just have helper methods that would execute those calls (and case it to int64 in case of freebsd).

)

// this abuses go so much that I felt dirty writing this code
// but it is the only way to do it without writing custom compiler that would
// be a clone of go-build with go-test
func TestRunMain(t *testing.T) {
func (m mainRun) TestRunMain(c *gc.C) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it will fly with coverage collection code. We will see when dependency thing is fixed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is breaking coverage collection func TestRunMain(t *testing.T) is run when testing starts and it takes over the testing process. In this case it just executes ipfs process not the tests. It is guided by the build flag of this file.

@Kubuxu
Copy link
Member

Kubuxu commented Mar 19, 2017

The ulimit raising is tested in sharness, so the unit tests are not needed 100% but useful nevertheless. If it causes problems with coverage collection we can move the ulimit raising to separate package and test it alone.

@hoenirvili
Copy link
Contributor Author

I'm open to any good solution. From my standpoint of view I think every file should contains a *_test.go. It's kind of scary that the project is missing so much unit testing.

@Kubuxu
Copy link
Member

Kubuxu commented Mar 19, 2017

Yes we are constantly trying to push for more unit testing. We have also quite a big sharness testing suite. Checkout test/sharness/

"github.com/ipfs/go-ipfs/commands"

gc "gopkg.in/check.v1"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dependency has to be rewritten, please run gx-go rewrite

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed !

@whyrusleeping
Copy link
Member

This needs to be broken up into logical atomic changesets

return fmt.Errorf("error setting ulimit without hard limit: %s", err)
}

log.Infof("Successfully raised file descriptor limit to %d.\n", maxFds)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Info is not default on channel so it won't be visible. We want this message to be visible during daemon start, that is the reason it was using fmt.Printf before.

@hsanjuan
Copy link
Contributor

Sorry to party-poop too, but there has been previous proposals to bring-in testing helper libraries and so far they have not moved forward. This would effectively introduce testing deps. See #3498

@hoenirvili
Copy link
Contributor Author

@hsanjuan check #3498

if !isClientError(&commands.Error{Code: commands.ErrClient}) {
t.Errorf("misidentified pointer")
}
func (m mainRun) TestIsCientErr(c *gc.C) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new way of testing this is not shorter, cleaner, or easier to understand. I'm not on board with this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree completely.
I think more mantainers of this project should take a look and give more feedback.
The c.Assert besides of line number and fail message it contains these lines as well "expected: value, obtained: value". which it gives us way more information on "what went wrong". If you want I can use testify to write these tests.
But keep relaying and using the testing pkg from std lib it's not good idea, in the long run. (in my opinion).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are free to have your opinions, but to convince me that this is worthwhile and not just some random contributor trying to push their opinions on a project they just found you'll have to do better convincing me of this.

You need to choose better examples to prove your point, because this specific one is not helped by shoving a test lib into it. "expected: true, got: false" Is quite clear from reading the existing test and the debuggability is not helped by adding in the extra cognitive overhead of this framework.

Copy link
Member

@Kubuxu Kubuxu Mar 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to agree with @whyrusleeping on this, in this case misidentified value is much more descriptive than expected: true, got: false.

Also replacing the c.Assert(ok, gc.Equals, true) with

if !ok {
    t.Error("misidentified value")
}

Is easier and faster to type, conveys more information and everybody knowns what is going on.
There is no value added of c.Assert in this part of code.

@hoenirvili hoenirvili changed the title Safe check rlimit syscall, refactoring and new dependency Safe check rlimit syscall and refactoring Mar 21, 2017
@hoenirvili
Copy link
Contributor Author

@whyrusleeping @Kubuxu now it's ok?

cmd/ipfs/init.go Outdated
@@ -9,6 +9,7 @@ import (
"path"

context "context"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while we're modifying this, lets go ahead and drop the named import (holdover from "golang.org/x/net/context") and move it up with the rest of the stdlib

@@ -5,15 +5,25 @@ import (
"strconv"
)

var ipfsFileDescNum = uint64(1024)
// maxFds is the maximum number of file descriptors that the go ipfs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/the go ipfs/go-ipfs/

@@ -5,15 +5,25 @@ import (
"strconv"
)

var ipfsFileDescNum = uint64(1024)
// maxFds is the maximum number of file descriptors that the go ipfs
// can use. Default values is 1024. This can be overwritten by the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/Default values/The default value/

}

// assign the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useless comment

// in the range from 0 up to the hard limit
if err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
// the process does not have permission so we should only set the soft value
if err == syscall.EPERM {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverse the logic here: if err != syscall.EPERM { ...

t.Logf("Testing file descriptor invalidity")
var err error
if err = os.Unsetenv("IPFS_FD_MAX"); err != nil {
t.Errorf("Cannot unset the IPFS_FD_MAX env variable")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use fatal for these, the rest of the test is invalid if this assertion (or the others) fails.

t.Errorf("Managefd should return an error")
}

if err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else if


package main

// managefd checks for the maximum number of file descriptors that the system supports
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is incorrect.

// the hard limit acts as a ceiling for the soft limit
// an unprivileged process may only set its soft limit to a value
// in the range from 0 up to the hard limit
if err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is nearly identical to the other freebsd logic. I'm sure we can DRY it up a bit

Copy link
Member

@whyrusleeping whyrusleeping left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments, lets try to DRY up the limit setting code so we don't mess up changes to it later


rlimit := syscall.Rlimit{}
if err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
t.Errorf("Cannot get the file descriptor count")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fatal here too as rest is useless if this fails

@Kubuxu
Copy link
Member

Kubuxu commented Mar 21, 2017

I would suggest pulling the fd managing code to separate package (it has nothing to do with ipfs main process itself) and also switching from directly referencing functions under build tag to using init to inject the platform specific code into a private variable. This way we don't have to have build tag for every not supported OS.

Example how it could be done:
Core file (no build tags):

// hasFDManage is used to determine if we should run tests
// (if we have generic, platform antagonistic test (open a lot of empty files)).
var hasFDManage bool = false
var manageFDs func() error = func() { return nil }

func ManageFDs() error {
    return manageFDs()
}

Then file that builds on unix:

func init() {
hasFDManage = true
manageFDs = unixManageFDs()
}

func unixManageFDs()

Then as freebsd is special, one file for non freebsd unix:

// +build unix,!freebsd

func getFDlimits...
func setFDlimits...

and separate one for freebsd:

// +build freebsd

func getFDlimits...
func setFDlimits...

return
}

// assign the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whats this comment here for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I will delete it.

@hoenirvili
Copy link
Contributor Author

Why not use the runtime.GOOS ?

@whyrusleeping
Copy link
Member

@hoenirvili I'm assuming youre asking why use build tags over a switch on runtime.GOOS. And my answer to that is "you can", but at some point you will have to deal with the fact that the syscall.RLimit has different types on different systems. The only decent way to deal with that is to use build tags and normalize it with some small wrapper code.

@hoenirvili
Copy link
Contributor Author

Can you please take a look one more time at the code? What changes should I make?

@whyrusleeping
Copy link
Member

@hoenirvili Something like this: https://gist.github.com/whyrusleeping/8c3bae34723f209665480f39d2163848

Let me know if i should be more detailed

"os"
"strconv"

"github.com/prometheus/common/log"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the wrong logging package

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import logging "github.com/ipfs/go-logging" and then do a global var log = logging.Logger("ulimit")

@whyrusleeping
Copy link
Member

The reasoning for the construction in the linked gist is to ensure that when trying this for new unsupported platforms, one: it doesnt fail to build because we forgot a special file with its particular build tags, and two: its much easier to implement support on new systems, just by implementing the getFDLimit and setFDLimit functions on the new platform.

@hoenirvili
Copy link
Contributor Author

Sorry for taking so much time... I was very busy these couple of weeks. I've updated the patch, you can take a look and tell what improvements I can add further.

@hoenirvili
Copy link
Contributor Author

What about now ?

@whyrusleeping
Copy link
Member

@hoenirvili Hey, I'll take another look at this tomorrow. Sorry for taking so long to get back to you here, i've been out for a week

@whyrusleeping whyrusleeping self-assigned this Apr 12, 2017
@hoenirvili
Copy link
Contributor Author

@whyrusleeping Thanks !

Copy link
Member

@Kubuxu Kubuxu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the comments and also CodeClimate issue.

// maxFds is the maximum number of file descriptors that go-ipfs
// can use. The default value is 1024. This can be overwritten by the
// IPFS_FD_MAX env variable
var maxFds = uint64(1024)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Master has increased it to 2048.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

}
}

func ManageFdLimit() (err error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the naming of the return value here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


func ManageFdLimit() (err error) {
if !supportsFDManagement {
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and do explicit nil or error return here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

// the hard limit acts as a ceiling for the soft limit
// an unprivileged process may only set it's soft limit to a
// alue in the range from 0 up to the hard limit
if err = setLimit(max, max); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure this is needed. Just setting it to max, hard should be ok (and before checking if max is < than hard).

// set the soft value
if max > hard {
return errors.New(
"cannot set rlimit, IPFS_FD_MAX is larger than the hard limit",
Copy link
Member

@Kubuxu Kubuxu Apr 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That might be not true, the builtin default rise might be larger than the hard limit

"strconv"
"syscall"

logging "github.com/ipfs/go-log"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run gx-go rw to fix it and allow tests to run

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@Kubuxu
Copy link
Member

Kubuxu commented Apr 21, 2017

Sharness test checking for the message is failing.

@hoenirvili
Copy link
Contributor Author

hoenirvili commented Apr 21, 2017

@Kubuxu Yes, I know but, I don't have experience with the sharness framework to modify the code in order to make the sharness test logic pass. Can you provide some advice on this topic?

@whyrusleeping
Copy link
Member

Hey @hoenirvili! Sorry for taking nearly a month to get back to you here. I pulled your branch down and figured out how to get the tests passing, here was my diff:

diff --git a/cmd/ipfs/util/ulimit.go b/cmd/ipfs/util/ulimit.go
index ed7f37fc1..d4669631a 100644
--- a/cmd/ipfs/util/ulimit.go
+++ b/cmd/ipfs/util/ulimit.go
@@ -49,12 +49,17 @@ func ManageFdLimit() error {
        }
 
        setMaxFds()
-       _, hard, err := getLimit()
+       soft, hard, err := getLimit()
        if err != nil {
                return err
        }
 
        max := int64(maxFds)
+
+       if max <= soft {
+               return nil
+       }
+
        // the soft limit is the value that the kernel enforces for the
        // corresponding resource
        // the hard limit acts as a ceiling for the soft limit
@@ -77,9 +82,8 @@ func ManageFdLimit() error {
                        return fmt.Errorf("error setting ulimit wihout hard limit: %s", err)
                }
 
-               fmt.Printf("Successfully raised file descriptor limit to %d.\n", max)
-               return nil
        }
+       fmt.Printf("Successfully raised file descriptor limit to %d.\n", max)
 
        return nil
 }

I also removed the file cmd/ipfs/ulimit.go since it is no longer needed

@hoenirvili
Copy link
Contributor Author

@whyrusleeping changed.

The soft limit is the value that the kernel enforces for the corresponding resource
The hard limit acts as a ceiling for the soft limit
an unprivileged process may only set its soft limit to a value
in the range from 0 up to the hard limit.
So in order to make the change in fds count without any error we should
inform the user to make the process have CAP_SYS_RESOURCE capability
in order to set the hard limit.

License: MIT
Signed-off-by: hoenirvili <[email protected]>
@ghost ghost assigned kevina Dec 4, 2017
@ghost ghost added the status/in-progress In progress label Dec 4, 2017
@kevina
Copy link
Contributor

kevina commented Dec 4, 2017

@whyrusleeping I just rebased

@whyrusleeping
Copy link
Member

@kevina thanks!

@hoenirvili Thanks for the changes! Sorry about the delay, i'm working on getting through the mountain of PRs now.

@whyrusleeping whyrusleeping merged commit 715bd16 into ipfs:master Dec 5, 2017
@ghost ghost removed the status/in-progress In progress label Dec 5, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants